Chuyển tới nội dung chính

Lớp ngoại lệ chuẩn trong C++

C++ cung cấp một hệ thống phân cấp các lớp ngoại lệ chuẩn trong thư viện <stdexcept><exception>, nhằm giúp người lập trình có thể ném và bắt các lỗi theo kiểu đối tượng.

Tất cả các lớp ngoại lệ chuẩn đều kế thừa từ lớp cơ sở std::exception (trừ một số ngoại lệ đặc biệt). Chúng được định nghĩa trong các tiêu đề (header) cụ thể của STL. Dưới đây là danh sách:

Cây phân cấp chính

Dưới đây là các nhánh chính trong hệ thống ngoại lệ chuẩn:

std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
└── std::runtime_error
├── std::range_error
├── std::overflow_error
├── std::underflow_error
└── std::ios_base::failure (từ <ios>)

1. std::exception

  • Thư viện: <exception>
  • Ý nghĩa: Lớp cơ sở cho tất cả các ngoại lệ chuẩn trong C++. Cung cấp giao diện cơ bản với phương thức ảo what() trả về mô tả lỗi dưới dạng chuỗi C (const char*).
  • Trường hợp sử dụng:
    • Dùng làm lớp cơ sở để bắt mọi ngoại lệ chuẩn (catch-all).
    • Thường dùng khi không cần xử lý ngoại lệ cụ thể
  • Ví dụ:
try { throw std::exception(); }
catch (const std::exception& e) { std::cerr << e.what(); }

2. std::logic_error

  • Thư viện: <stdexcept>
  • Ý nghĩa: Lớp cơ sở cho các ngoại lệ liên quan đến lỗi logic trong mã (lỗi có thể tránh được nếu mã được viết đúng).
  • Trường hợp sử dụng: Dùng cho các lỗi do logic chương trình, như truyền tham số không hợp lệ.
  • Các lớp con:
    • std::invalid_argument
      • Ý nghĩa: Ném khi một hàm nhận tham số không hợp lệ.
      • Trường hợp sử dụng: Ví dụ, truyền chuỗi không phải số vào std::stoi hoặc gán giá trị không hợp lý.
      • Ví dụ:
        try { std::stoi("abc"); }
        catch (const std::invalid_argument& e) { std::cerr << "Invalid argument: " << e.what(); }
    • std::domain_error
      • Ý nghĩa: Ném khi giá trị đầu vào nằm ngoài miền giá trị hợp lệ của một hàm toán học.
      • Trường hợp sử dụng: Ví dụ, tính std::sqrt(-1) trong một số thư viện toán học.
      • Ví dụ:
        try { throw std::domain_error("Negative value for sqrt"); }
        catch (const std::domain_error& e) { std::cerr << e.what(); }
    • std::length_error
      • Ý nghĩa: Ném khi vượt quá giới hạn độ dài của một đối tượng (như chuỗi hoặc vector).
      • Trường hợp sử dụng: Ví dụ, gọi std::vector::resize với kích thước vượt quá max_size().
      • Ví dụ:
        std::vector<int> v;
        try { v.resize(v.max_size() + 1); }
        catch (const std::length_error& e) { std::cerr << e.what(); }
    • std::out_of_range
      • Ý nghĩa: Ném khi truy cập ngoài phạm vi của một đối tượng (như chỉ số vector không hợp lệ).
      • Trường hợp sử dụng: Ví dụ, truy cập std::vector::at(index) với index ngoài giới hạn.
      • Ví dụ:
        std::vector<int> v = {1, 2, 3};
        try { v.at(10); }
        catch (const std::out_of_range& e) { std::cerr << e.what(); }
    • std::range_error
      • Ý nghĩa: Ném khi giá trị nằm ngoài phạm vi biểu diễn của kiểu dữ liệu.
      • Trường hợp sử dụng: Ít gặp trong STL, chủ yếu dùng trong các thư viện toán học hoặc khi giá trị vượt quá giới hạn kiểu dữ liệu.
      • Ví dụ:
        try { throw std::range_error("Value too large"); }
        catch (const std::range_error& e) { std::cerr << e.what(); }

3. std::runtime_error

  • Thư viện: <stdexcept>
  • Ý nghĩa: Lớp cơ sở cho các ngoại lệ xảy ra trong quá trình chạy chương trình, thường không thể dự đoán trước.
  • Trường hợp sử dụng: Dùng cho các lỗi runtime như thất bại khi cấp phát bộ nhớ hoặc lỗi I/O.
  • Các lớp con:
    • std::overflow_error
      • Ý nghĩa: Ném khi xảy ra tràn số (overflow) trong tính toán.
      • Trường hợp sử dụng: Ví dụ, tính toán vượt quá giới hạn của kiểu dữ liệu (ít gặp trong STL, thường trong thư viện toán học).
      • Ví dụ:
        try { throw std::overflow_error("Arithmetic overflow"); }
        catch (const std::overflow_error& e) { std::cerr << e.what(); }
    • std::underflow_error
      • Ý nghĩa: Ném khi xảy ra thiếu số (underflow) trong tính toán, ví dụ với số thực dấu phẩy động.
      • Trường hợp sử dụng: Thường dùng trong các thư viện toán học.
      • Ví dụ:
        try { throw std::underflow_error("Arithmetic underflow"); }
        catch (const std::underflow_error& e) { std::cerr << e.what(); }
    • std::bad_alloc
      • Thư viện: <new>
      • Ý nghĩa: Ném khi thất bại trong việc cấp phát bộ nhớ động (ví dụ, khi new không thể cấp phát).
      • Trường hợp sử dụng: Xử lý lỗi khi hết bộ nhớ.
      • Ví dụ:
        try { int* p = new int[std::numeric_limits<size_t>::max()]; }
        catch (const std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what(); }
    • std::bad_array_new_length
      • Thư viện: <new>
      • Ý nghĩa: Ném khi cấp phát mảng với kích thước không hợp lệ (âm hoặc quá lớn).
      • Trường hợp sử dụng: Xảy ra khi dùng new để cấp phát mảng với kích thước không hợp lý.
      • Ví dụ:
        try { int* p = new int[-1]; }
        catch (const std::bad_array_new_length& e) { std::cerr << e.what(); }

4. std::bad_cast

  • Thư viện: <typeinfo>
  • Ý nghĩa: Ném khi ép kiểu động (dynamic_cast) thất bại.
  • Trường hợp sử dụng: Dùng khi ép kiểu một con trỏ hoặc tham chiếu sang kiểu không tương thích trong hệ thống kế thừa.
  • Ví dụ:
    struct Base { virtual ~Base() {} };
    struct Derived : Base {};
    Base* b = new Base;
    try { dynamic_cast<Derived&>(*b); }
    catch (const std::bad_cast& e) { std::cerr << "Bad cast: " << e.what(); }

5. std::bad_typeid

  • Thư viện: <typeinfo>
  • Ý nghĩa: Ném khi sử dụng typeid trên một con trỏ null hoặc đối tượng không hợp lệ.
  • Trường hợp sử dụng: Xảy ra khi truy cập thông tin kiểu của một đối tượng không có RTTI (Run-Time Type Information).
  • Ví dụ:
    struct Base { virtual ~Base() {} };
    Base* b = nullptr;
    try { typeid(*b); }
    catch (const std::bad_typeid& e) { std::cerr << "Bad typeid: " << e.what(); }

6. std::bad_function_call

  • Thư viện: <functional>
  • Ý nghĩa: Ném khi gọi một std::function rỗng (không được gán hàm mục tiêu).
  • Trường hợp sử dụng: Dùng khi cố gắng gọi một std::function chưa được khởi tạo.
  • Ví dụ:
    std::function<void()> f;
    try { f(); }
    catch (const std::bad_function_call& e) { std::cerr << "Bad function call: " << e.what(); }

7. std::ios_base::failure

  • Thư viện: <ios>
  • Ý nghĩa: Ném khi xảy ra lỗi trong thao tác I/O, như đọc/ghi file thất bại.
  • Trường hợp sử dụng: Dùng khi làm việc với std::fstream, std::cin, std::cout (phải bật ngoại lệ bằng ios::exceptions()).
  • Ví dụ:
    std::ifstream file("nonexistent.txt");
    file.exceptions(std::ios::failbit);
    try { file >> std::ws; }
    catch (const std::ios_base::failure& e) { std::cerr << "I/O error: " << e.what(); }

8. std::bad_weak_ptr

  • Thư viện: <memory>
  • Ý nghĩa: Ném khi truy cập một std::shared_ptr từ một std::weak_ptr đã hết hạn (expired).
  • Trường hợp sử dụng: Dùng khi làm việc với std::weak_ptr và đối tượng quản lý đã bị hủy.
  • Ví dụ:
    std::weak_ptr<int> wp;
    {
    std::shared_ptr<int> sp = std::make_shared<int>(10);
    wp = sp;
    } // sp bị hủy, wp hết hạn
    try { std::shared_ptr<int> sp = wp.lock(); if (!sp) throw std::bad_weak_ptr(); }
    catch (const std::bad_weak_ptr& e) { std::cerr << "Bad weak_ptr: " << e.what(); }

9. std::future_error

  • Thư viện: <future>
  • Ý nghĩa: Ném khi xảy ra lỗi liên quan đến std::future hoặc std::promise, như truy cập future không hợp lệ.
  • Trường hợp sử dụng: Dùng trong lập trình bất đồng bộ (asynchronous programming).
  • Ví dụ:
    std::promise<int> p;
    std::future<int> f = p.get_future();
    try { f.get(); } // promise chưa được gán giá trị
    catch (const std::future_error& e) { std::cerr << "Future error: " << e.what(); }

10. std::system_error

  • Thư viện: <system_error>
  • Ý nghĩa: Ném khi xảy ra lỗi hệ thống (liên quan đến hệ điều hành hoặc thư viện hệ thống).
  • Trường hợp sử dụng: Dùng trong các hàm như std::thread, std::mutex, hoặc các thao tác hệ thống khác.
  • Ví dụ:
    try { std::thread t([]{}); t.detach(); t.detach(); } // Lỗi: detach thread đã detached
    catch (const std::system_error& e) { std::cerr << "System error: " << e.what(); }

Hàm what()

Tất cả các lớp ngoại lệ chuẩn đều có hàm what():

try {
throw std::invalid_argument("Tham số không hợp lệ!");
} catch (const std::exception& e) {
std::cout << e.what(); // In ra thông báo lỗi
}
  • Hàm what() hữu ích cho logging, in lỗi, và debug.

Lưu ý

  • Ưu tiên bắt ngoại lệ cụ thể: Thay vì chỉ bắt std::exception, hãy bắt các lớp ngoại lệ cụ thể (như std::invalid_argument, std::bad_alloc) để xử lý lỗi chính xác hơn. Nhưng để đơn giản và an toàn, có thể bắt std::exception nếu chỉ cần thông báo lỗi.
  • Sử dụng try-catch hợp lý: Chỉ dùng try-catch cho các đoạn mã có khả năng ném ngoại lệ (ví dụ, cấp phát bộ nhớ lớn, truy cập container, hoặc I/O).
  • Kết hợp với kiểm tra điều kiện: Với các lỗi có thể kiểm tra trước (như kiểm tra chỉ số vector), dùng điều kiện thay vì dựa vào ngoại lệ để tăng hiệu suất.
  • Đảm bảo tài nguyên được giải phóng: Sử dụng RAII (Resource Acquisition Is Initialization) để tránh rò rỉ tài nguyên khi ngoại lệ xảy ra.
  • Một số ngoại lệ ít gặp trong thực tế (như std::underflow_error, std::range_error) thường chỉ xuất hiện trong các thư viện toán học hoặc triển khai đặc biệt.
  • Nếu bạn sử dụng các thư viện bên ngoài hoặc mã tùy chỉnh, có thể có các ngoại lệ khác không thuộc STL.