Xử lý ngoại lệ – Exception Handling trong C++

Bản thân C++ không có khái niệm “errors – lỗi”. Khi mọi thứ xảy ra không bình thường, không như kỳ vọng thì C++ gọi đó là “exception – ngoại lệ”, “exception” có nghĩa rộng hơn “error”. Tất cả các error đều là exception, nhưng không phải tất cả exception đều là error. Khi có điều gì đó sai sai trong một chương trình, một exception phát sinh. Exceptions phát sinh theo 2 cách, trường hợp thứ nhất là phát sinh tự động từ các hàm trong thư viện của C++, hoàn toàn ngoài tầm kiểm soát của chúng ta, trong khi trường hợp khác được phát sinh thủ công theo ý muốn của người phát triển.

Một exception cũng chính là data, là dữ liệu. Hãy tưởng tượng một exception như một chiếc hộp dữ liệu có cánh, có khả năng bay, xuất hiện khi một cái gì đó sai sai xảy ra. Hộp chứa dữ liệu có thể giúp người phát triển xác định nguyên nhân của lỗi. Dữ liệu ở đấy có thể là bất kỳ kiểu dữ liệu nào: nó có thể là kiểu int, float, string hay một đối tượng của class bất kỳ nào đó. Bạn sẽ phải chọn kiểu dữ liệu hữu ích nhất trong trường hợp của mình.

Xử lý exception – exception handling trong C++

Các đoạn code có khả năng gây ra lỗi cần phải được đánh dấu trong một loại block code đặc biệt. Block đó sẽ được theo dõi cẩn thận trong quá trình thực thi. Một exception mới phát sinh được mô tả là “một exception đã bị throw (ném ra)”. Khi một exception được ném ra, việc thực thi của block code đó sẽ chấm dứt, nhưng bản thân chương trình vẫn còn sống. Exception mới phát sinh bắt đầu bay mang theo dữ liệu và bay đến cuối của block. Nếu không ai muốn bắt exception , nó vẫn tiếp tục bay đến cấp cao hơn của chuỗi functions với hy vọng rằng có ai đó muốn catch (bắt) nó. Nếu exception không bị catch ở cấp cao nhất (trong hàm main()), điều này sẽ khiến chương trình dừng lại và phát ra thông báo chẩn đoán thích hợp. Đây là kịch bản xấu nhất.

Trong C++ thì xử lý exception được thực hiện thông qua 3 keywords chính là: try, catch, throw.
  • try − Nếu bạn muốn phát hiện các exception, bạn cần đánh dấu các phần code mà trong đó các exception có thể xảy ra. Bạn làm điều này bằng cách sử dụng câu lệnh try. Trên thực tế, câu lệnh try không thực sự làm bất cứ điều gì đặc biệt, nó không thay đổi việc thực thi của phần code bên trong nó. Nó chỉ kích hoạt chức năng quan sát và theo dõi các exception →

Như bạn có thể thấy, tên của câu lệnh rất trực quan sinh động – thử thực thi đoạn code bên trong xem điều gì xảy ra.

  • throw − Khi có bất thường xảy ra trong chương trình, sử dụng keyword throw để ném ra một ngoại lệ.
  • catch − Nếu bạn muốn bắt bất kỳ exception nào, thì bạn cần phải đặt câu lệnh catch sau câu lệnh try. Có thể coi câu lệnh catch như một hàm không có tên với một tham số xác định kiểu của exception. Lệnh catch sẽ chỉ bắt những exception tương thích với kiểu đã được chỉ định.

Giả sử một đoạn code nào đó có thể sẽ ném ra một ngoại lệ, một hàm sẽ bắt một ngoại lệ bằng cách sử dụng kết hợp các từ khóa trycatch. Một khối try / catch được rào xung quanh đoạn code có thể ném ra ngoại lệ. Code trong khối lệnh try được gọi là code được bảo vệ. Ví dụ về cú pháp sử dụng try / catch như sau →

Có thể dùng nhiều câu lệnh catch để bắt các exception có kiểu khác nhau.

Throwing Exceptions – Ném ngoại lệ

Nếu bạn muốn “ném” một exception, bạn phải sử dụng câu lệnh throw. Câu lệnh throw cần phải được cung cấp dữ liệu mà sẽ được “đóng gói” thành một exception. Nếu bạn định gửi giá trị int, bạn sẽ phải viết một cái gì đó kiểu như thế này →

Nếu bạn muốn ném một exception mang theo một đối tượng của class bất kỳ nào, bạn cần phải gọi constructor của class đó để chuẩn bị dữ liệu, ví dụ một exception kiểu string sẽ như sau →

Sau đây là một ví dụ về việc ném một ngoại lệ khi chia cho điều kiện 0 xảy ra →

Catching Exceptions – Bắt ngoại lệ

Nếu bạn muốn bắt bất kỳ exception nào, thì bạn cần phải đặt câu lệnh catch sau câu lệnh try. Có thể coi câu lệnh catch như một hàm không có tên với một tham số xác định kiểu của exception. Lệnh catch sẽ chỉ bắt những exception tương thích với kiểu đã được chỉ định.

Đoạn code trên sẽ bắt một exception được ném ra nởi protected code trong khối try nếu exception đó có kiểu là ExceptionType. Nếu muốn lệnh catch bắt exception thuộc bất kỳ kiểu dữ liệu nào thì không cần chỉ định kiểu của exception mà thay vào đó là dấu “…”. Ví dụ như sau →

Sau đây là một ví dụ ném ra một exception khi xảy ra lỗi chia cho 0, và thực hiện bắt nó trong với câu lệnh catch →

Bời vì chúng ta ném ra 1 exception kiểu const char* nên khi bắt exception này chúng ta cần sử dụng kiểu const char* trong lệnh catch. Nếu chúng ta biên dịch và chạy ví dụ trên thì kết quả in ra màn hình như sau

Các exception chuẩn của C++

C++ cung cấp một danh sách exception chuẩn mà chúng ta có thể sử dụng được luôn, chúng được định nghĩa trong file header <exception>.

NoKiểuÝ nghĩa (Giải thích sơ bộ)
1std::exceptionException chung nhất và là class cụ tổ của tất cả các exception chuẩn khác trong C++
2std::bad_allocCấp phát bộ nhớ không thành công. Có thể được ném ra bởi toán tử new
3std::bad_castÉp kiểu động không thành công. Có thể được ném ra bởi dynamic_cast
4std::bad_exceptionCó một exception nào đó được ném ra một cách không mong muốn
5std::bad_typeidĐược ném ra khi toán tử typeid thực hiện trên một con trỏ null
6std::logic_errorCác lỗi liên quan tới logic, thuật toán, tính hợp lệ của dữ liệu. Là class cha của các exception liên quan đến logic
7std::domain_errorDữ liệu vượt quá khoảng cho phép
8std::invalid_argumentTruyền tham số không đúng cách
9std::length_errorSử dụng các giá trị không hợp lệ để chỉ định kích thước / độ dài của tập hợp dữ liệu
10std::out_of_rangeSử dụng các indexs / keys không hợp lệ trong khi truy cập các bộ dữ liệu được đánh số / key
11std::runtime_errorNó được thiết kế để đại diện cho tất cả các exceptions gây ra bởi các tình huống có thể xảy ra trong quá trình chạy của chương trình
12std::overflow_errorTràn bộ nhớ do dữ liệu quá lớn so với kích thước bộ nhớ dùng để chứa nó
13std::range_errorKết quả tính toán vượt quá khoảng cho phép
14std::underflow_errorDữ liệu quá nhỏ để có thể biểu diễn giá trị có ý nghĩa

Tham khảo

— Phạm Minh Tuấn (Shun) —