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:
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:
- 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
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.