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, throw và catch.
Dạng thức sử dụng như sau:
Nguyên tắc hoạt động: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 }
- Đ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 <iostream.h> 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 <iostream.h> 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:
- 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).
- 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 <exception> và được dùng làm mẫu cho hệ thống phân cấp các exception chuẩn: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++)
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)
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 <iostream.h> #include <exception> #include <typeinfo> 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.