Mastering C++ Standard Exceptions: From Basics to Custom Errors
This article explains why using C++ standard exception classes is essential, outlines the exception hierarchy, details common logical and runtime error types, demonstrates throwing and catching exceptions with code examples, and shows how to create custom exception classes for robust error handling.
In previous chapters we learned how to use try, catch and throw to build a basic C++ exception handling framework. Throwing primitive types (e.g., int, string) is a crude practice that lacks detailed error information and hampers classification. The C++ standard library provides a powerful hierarchy of standard exceptions based on std::exception. This chapter explores that hierarchy, which is key to writing robust and maintainable C++ programs.
1. Why use standard exception classes?
Before introducing specific classes, understand the benefits of using standard exception classes:
Type safety: throwing class objects is type‑safe compared to throwing an int or char*.
Polymorphism: you can catch the base std::exception to handle all derived exceptions, or catch specific derived classes for particular errors.
Rich error information: standard exception classes usually provide a what() member that returns a descriptive C‑style string ( const char*), offering far more context than an error code.
Standardization and readability: using standard exceptions makes code easier for other C++ developers to understand and maintain.
Hierarchy: standard exceptions are organized in a hierarchy, allowing fine‑grained catching based on error categories.
2. Standard exception class hierarchy
The core inheritance hierarchy of C++ standard exceptions (simplified) is:
std::exception
│
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::out_of_range
│ ├── std::length_error
│ └── ...
│
├── std::runtime_error
│ ├── std::range_error
│ ├── std::overflow_error
│ ├── std::underflow_error
│ ├── std::system_error
│ └── ...
│
└── ... (other like std::bad_alloc, std::bad_cast, etc.)Exceptions are mainly divided into two large categories:
Logic errors ( std::logic_error and its derivatives): these errors cannot be detected at compile time but stem from internal program logic, such as passing an invalid argument, out‑of‑range index, or creating a container with a negative size.
Runtime errors ( std::runtime_error and its derivatives): these occur during program execution due to external conditions or uncontrollable input, such as file‑open failure, network interruption, numeric overflow, or type‑conversion failure.
Other exceptions inherit directly from std::exception, for example std::bad_alloc when memory allocation fails.
3. Common standard exception classes
Base class: std::exception
This is the base of all standard exception classes. It defines a virtual interface, the most important member function being: virtual const char* what() const noexcept; The function returns a string describing the exception. Derived classes override it to provide more specific information.
Common logical‑error derived classes
std::invalid_argument : indicates that a function received an unacceptable argument.
#include <stdexcept>
#include <iostream>
void setAge(int age) {
if (age < 0 || age > 150) {
throw std::invalid_argument("Age must be between 0 and 150.");
}
// ... normal processing
}
int main() {
try {
setAge(-5);
} catch (const std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << std::endl;
}
return 0;
}std::out_of_range : indicates that an argument is outside the allowed range, commonly used when accessing containers.
#include <vector>
#include <stdexcept>
int getElement(const std::vector<int>& vec, size_t index) {
if (index >= vec.size()) {
throw std::out_of_range("Index is out of the vector's range.");
}
return vec[index];
}std::length_error : indicates an attempt to create an object whose size exceeds the maximum allowed for that type, such as constructing a very large std::string or std::vector .
Common runtime‑error derived classes
std::runtime_error : serves as a base for many runtime errors and can be used for general runtime failures.
#include <fstream>
#include <stdexcept>
void openConfigFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::runtime_error("Failed to open config file: " + filename);
}
// ... normal processing
}std::overflow_error / std::underflow_error : represent arithmetic overflow and underflow respectively.
std::system_error (C++11): wraps operating‑system or library error codes (e.g., errno ) and provides richer system‑error information than std::runtime_error .
4. How to use: throw and catch
Throwing standard exceptions
Instantiate and throw the appropriate exception class, usually passing a descriptive string to its constructor.
throw std::runtime_error("Sensor data reading timed out");Catching standard exceptions
Leverage polymorphism to catch from specific to general:
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
try {
int value = getElement(vec, 10); // may throw std::out_of_range
} catch (const std::out_of_range& e) {
std::cerr << "Specific out_of_range error: " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cerr << "Logic error detected: " << e.what() << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Standard exception caught: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught!" << std::endl;
}
return 0;
}Best practice: place derived‑class catch blocks before base‑class ones; catching std::exception first would prevent later derived catches from executing.
5. Custom exception classes
You can inherit from std::exception or its derived classes (commonly std::runtime_error or std::logic_error) to create domain‑specific exceptions.
#include <stdexcept>
#include <string>
class MyBusinessException : public std::runtime_error {
public:
MyBusinessException(const std::string& err_code, const std::string& message)
: std::runtime_error(message), m_err_code(err_code) {}
const char* getErrorCode() const noexcept { return m_err_code.c_str(); }
private:
std::string m_err_code;
};
void processTransaction() {
if (/* insufficient balance */) {
throw MyBusinessException("ERR_INSUFFICIENT_FUNDS", "Account balance is too low.");
}
}Summary
std::exceptionand its derived classes provide a powerful, flexible, and standardized error‑reporting mechanism for C++ programs. Abandon the habit of throwing primitive types and adopt standard exceptions to greatly improve code robustness, readability, and maintainability. Remember:
Prefer standard exceptions over custom or primitive types.
Choose logical‑error classes for programming mistakes and runtime‑error classes for external failures.
Use the what() method to obtain error descriptions.
Catch exceptions from most specific to most general.
Create custom exception classes by inheriting when domain‑specific error information is needed.
Mastering standard exception classes is an essential step toward becoming an excellent C++ developer.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
php Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
