The C++ exception handling mechanism allows a program to transfer control from the point of error to specialized error handling code when a runtime error occurs.
The core mechanism is based on three keywords: throw, try, and catch.
Key components:
1. throw
When an error is detected, an exception object is thrown using throw. Any type can be thrown (basic types, class objects, pointers, etc.), but it is recommended to use standard exception classes.
2. try
Wraps the code block that may throw an exception. Once an exception is thrown, the program immediately exits the try block.
3. catch
Catches and handles specific types of exceptions. catch blocks are matched in order, supporting multiple type catches and ellipsis (…) to catch all exceptions.
Execution flow:
1. An exception is thrown in the try block
2. Exit the current scope (stack unwinding, calling destructors of local objects)
3. Match catch blocks in order
4. If a match is found, execute the handling code; otherwise, continue stack unwinding
5. If no matching handler is found, call std::terminate() to terminate the program
Code Example
Example 1: Basic Exception Handling
#include <iostream>#include <stdexcept> // Standard exception definitions
double divide(double a, double b) { if (b == 0) { // Throw standard exception class object throw std::runtime_error("Division by zero!"); } return a / b;}
int main() { try { double result = divide(10, 0); std::cout << "Result: " << result << "\n"; } catch (const std::runtime_error& e) { // Catch specific exception std::cerr << "Error: " << e.what() << "\n"; } catch (...) { // Catch all other exceptions std::cerr << "Unknown error occurred!\n"; } return 0;}
Output: Error: Division by zero!
Example 2: Custom Exception Class
#include <iostream>#include <string> // Custom exception class (inherits std::exception)
class InvalidInputException : public std::exception { std::string msg;public: InvalidInputException(const std::string& s) : msg(s) {} const char* what() const noexcept override { return msg.c_str(); }};
void processAge(int age) { if (age < 0 || age > 120) { throw InvalidInputException("Invalid age: " + std::to_string(age)); } std::cout << "Age processed: " << age << "\n";}
int main() { try { processAge(-5); // Trigger exception } catch (const InvalidInputException& e) { std::cerr << "Custom Error: " << e.what() << "\n"; } catch (const std::exception& e) { // Catch base class exception std::cerr << "Standard Error: " << e.what() << "\n"; } return 0;}
Output: Custom Error: Invalid age: -5
Example 3: Stack Unwinding and Resource Management (RAII)
#include <iostream>#include <memory>
class Resource {public: Resource() { std::cout << "Resource acquired\n"; } ~Resource() { std::cout << "Resource released\n"; }};
void riskyOperation() { Resource res; // RAII object throw std::logic_error("Operation failed!"); // When the exception is thrown, res will be automatically destructed}
int main() { try { riskyOperation(); } catch (const std::exception& e) { std::cerr << "Caught: " << e.what() << "\n"; } return 0;}
Output:
Resource acquired
Resource released
Caught: Operation failed!
Key Considerations:
1. Exception Safety
Use RAII (Resource Acquisition Is Initialization) to manage resources (such as smart pointers, file handles, etc.) to ensure that resources are not leaked when exceptions occur.
2. Exception Types
Prefer using standard exceptions (defined in stdexcept):
std::runtime_error: Runtime error
std::logic_error: Program logic error
std::out_of_range: Out of bounds access
Custom exceptions should inherit from std::exception.
3. Performance Considerations
The exception handling mechanism has no overhead on the normal path. The overhead is significant when throwing exceptions (stack unwinding, type matching, etc.), so it should not be used for regular control flow.
4. Best Practices
Arrange catch blocks from specific to abstract by exception type. Use const& to catch exceptions to avoid copying, and resource cleanup should be done through destructors rather than catch blocks.
// Correct: Sort by derived class to base class
try { /* ... */ }catch (const MyException& e) { ... } // Specific exception
catch (const std::exception& e) { ... } // General exception
catch (...) { ... } // Fallback handling
By using the exception mechanism appropriately, the robustness and maintainability of the code can be significantly improved, but care should be taken to avoid overuse, which can lead to performance degradation or code structure complexity.