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>
và <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ớiindex
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(); }
- Thư viện:
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(); }
- Thư viện:
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ằngios::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ộtstd::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ặcstd::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ắtstd::exception
nếu chỉ cần thông báo lỗi. - Sử dụng
try-catch
hợp lý: Chỉ dùngtry-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.