Bài 5.3
Exception handling

Exception handling là một tính năng mới được giới thiệu bởi chuẩn ANSI-C++. Nếu bạn sử dụng một trình biên dịch C++ không tương thích với chuẩn ANSI C++ thì bạn không thể sử dụng tính năng này.
Trong suốt quá trình phát triển một chương trình, có thể có một số trường hợp mà một số đoạn mã chạy sai do truy xuất đến những tài nguyên không tồn tại hay vượt ra ngoài khoảng mong muốn...
Những loại tình huống bất thường này được nằm trong cái được gọi là exceptions và C++ đã vừa tích hợp ba toán tử mới để xử lý những tình huống này: try, throwcatch.
Dạng thức sử dụng như sau:
try {
// đoạn mã cần thử
throw exception;
}
catch (
type  exception)
{
// đoạn được thực hiện trong trường hợp có lỗi
}
Nguyên tắc hoạt động:
- Đoạn mã nằm trong khối try được thực hiện một cách bình thường. Trong trường hợp có lỗi xảy ra, đoạn mã này phải sử dụng từ khoá throw và một tham số để báo lỗi. Kiểu tham số này mô tả chi tiết hoá lỗi và có thể là bất kì kiểu hợp lệ nào.
- Nếu có lỗi xảy ra, nếu lệnh throw đã được thực hiện bên trong khối try, khối catch sẽ được thực hiện và nhận tham số được truyền bởi throw.
Ví dụ:

// exceptions
#include
int main () {
char myarray[10];
try
{
for (int n=0; n<=10; n++)
{
if (n>9) throw "Out of range";
myarray[n]='z';
}
}
catch (char ° str)
{
cout << "Exception: " << str << endl;
}
return 0;
}
Exception: Out of range

Trong ví dụ này, nếu bên trong vòng lặp mà n lớn hơn 9 thì một lỗi sẽ được thông báo vì  myarray[n] trong trường hợp đó có thể trỏ đến địa chỉ ô nhớ không tin cậy. Khi throw được thực hiện, khối try ngay lập tức kết thúc và mọi đối tượng được tạo bên trong khối try bị phá huỷ. Sau đó, quyền điều khiển được chuyển cho khối catch tương ứng (chỉ được thực hiện trong những tình huống như thế này). Cuối cùng chương trình tiếp tục ngay sau khối, trong trường hợp này: return 0;.
Cú pháp được sử dụng bởi throw tương tự với return: Chỉ có một tham số và không cần đặt nó nằm trong cặp ngoặc đơn.
Khối catch phải nằm ngay sau khối try mà không được có đoạn mã nào nằm giữa chúng. Tham số mà catch chấp nhận có thể là bất kì kiểu dữ liệu hợp lệ nào. Hơn nữa, catch có thể được quá tải để có thể chấp nhận nhiều kiểu dữ liệu khác nhau. Trong trường hợp này khối catch được thực hiện là khối phù hợp với kiểu của tham số được gửi đến bởi throw:

// exceptions: multiple catch blocks
#include
int main () {
try
{
char ° mystring;
mystring = new char [10];
if (mystring == NULL) throw "Allocation failure";
for (int n=0; n<=100; n++)
{
if (n>9) throw n;
mystring[n]='z';
}
}
catch (int i)
{
cout << "Exception: ";
cout << "index " << i << " is out of range" << endl;
}
catch (char ° str)
{
cout << "Exception: " << str << endl;
}
return 0;
}
Exception: index 10 is out of range

Ở đây có thể có hai trường hợp xảy ra:
  1. Khối dữ liệu 10 kí tự không thể được cấp phát (gần như là chẳng bao giờ xảy ra nhưng không có nghĩa là không thể): lỗi này sẽ bị chặn bởi catch (to char ° str).
  2. Chỉ số cực đại của mystring đã bị vượt quá: lỗi này sẽ bị chặn bởi catch (int i), since parameter is an integer number.
Chúng ta có thể định nghĩa một khối catch để chặn tất cả các exceptions mà không phụ thuộc vào kiểu được dùng để gọi throw. Để làm việc này chúng ta phải viết dấu ba chấm thay vì kiểu và tên số tham số:
try {
// code here
}
catch (...) {
cout << "Exception occurred";
}
Còn có thể lồng các khối try-catch vào các khối try khác. Trong trường hợp này, một khối catch bên trong có thể chuyển tiếp exception nhận được cho khối bên ngoài, để làm việc này chúng ta sử dụng biểu thức  throw; không có tham số. Ví dụ:
try {
try {
// code here
}
catch (int n) {
throw;
}
}
catch (...) {
cout << "Exception occurred";
}

Exception không bị chặn

Nếu một exception không bị chặn bởi bất kì lệnh catch nào vì không có lệnh nào có kiểu phù hợp, hàm đặc biệt terminate sẽ được gọi.
Hàm này đã được định nghĩa sẵn để chấm dứt chương trình ngay lập tức và hiển thịc thông báo lỗi "Abnormal termination". Dạng thức của nó như sau:
void terminate();

Những exceptions chuẩn

Một số hàm thuộc thư viện C++ chuẩn gửi các exceptions mà chúng ta có thể chặn nếu chúng ta sử dụng một khối try. Những exceptions này được gửi đi với kiểu tham số là một lớp thừa kế từ std::exception. Lớp này (std::exception) được định nghĩa trong file header C++ chuẩn và được dùng làm mẫu cho hệ thống phân cấp các exception chuẩn:
exception
bad_alloc(gửi bởi new)
bad_cast(gửi bởi dynamic_cast khi thất bại với một kiểu tham chiếu)
bad_exception(được gửi khi một exception không phù hợp với lệnh catch nào)
bad_typeid(gửi bởi typeid)
logic_error
domain_error
invalid_argument
length_error
out_of_range
runtime_error
overflow_error
range_error
underflow_error
ios_base::failure(gửi bởi ios::clear)
Bởi vì đây là một hệ thống phân lớp có thứ bậc, nếu bạn sử dụng một khối catch để chặn bất kì một exception nào nằm trong hệ thông này bằng cách sử dụng tham số biến (thêm một dấu & vào phía trước tên của tham số) bạn sẽ chặn được tất cả các exception thừa kế (luật thừa kế trong C++)
Ví dụ dưới đây chặn một exception có kiểu bad_typeid (được thừa kế từ exception), lỗi này được tạo ra khi muốn biết kiểu của một con trỏ null.

// Những exception chuẩn
#include
#include
#include
class A {virtual f() {}; };
int main () {
try {
A ° a = NULL;
typeid (°a);
}
catch (std::exception& e)
{
cout << "Exception: " << e.what();
}
return 0;
}
Exception: Attempted typeid of NULL pointer

Bạn có thể sử dụng các lớp của hệ thống phân cấp các exception chuẩn này báo những lỗi của mình hoặc thừa kế những lớp mới từ chúng.

Xem Tiếp: ----