Background #
In C++ programs, errors such as invalid input, missing files, or failed operations can occur unexpectedly. Proper handling of these errors is critical to avoid crashes and maintain program stability. C++ exception handling provides a structured mechanism to detect and respond to runtime errors.
Modern Exception Handling Concepts #
1. throw: Raising Exceptions #
Use throw
to signal an error condition. Always throw exceptions derived from std::exception
for compatibility and clarity.
#include <stdexcept>
#include <string>
void processInput(int value) {
if (value < 0) {
throw std::invalid_argument("Value must be non-negative");
}
// Continue processing...
}
2. try: Protected Code Block #
Wrap potentially risky code inside a try
block. Only code that may fail should go inside.
try {
processInput(-5);
} catch (const std::invalid_argument& e) {
std::cerr << "Input error: " << e.what() << "\n";
}
3. catch: Handling Exceptions #
Catch specific exception types first, followed by a generic handler if needed. Prefer const references to avoid unnecessary copies.
try {
// Code that may throw exceptions
processInput(-10);
} catch (const std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << "\n";
} catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cerr << "Other exception: " << e.what() << "\n";
} catch (...) {
std::cerr << "Unknown exception occurred\n";
}
4. RAII: Prevent Resource Leaks #
Use RAII (Resource Acquisition Is Initialization) to manage resources automatically. This ensures resources are released even if an exception occurs.
#include <fstream>
#include <iostream>
#include <string>
void readFile(const std::string& filename) {
std::ifstream file(filename); // RAII automatically closes the file
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + filename);
}
std::string line;
while (std::getline(file, line)) {
std::cout << line << "\n";
}
}
int main() {
try {
readFile("data.txt");
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}
5. Custom Exception Classes #
For more complex applications, define custom exception classes derived from std::exception
:
#include <exception>
#include <string>
class MyException : public std::exception {
public:
explicit MyException(const std::string& message) : msg_(message) {}
const char* what() const noexcept override {
return msg_.c_str();
}
private:
std::string msg_;
};
void riskyOperation() {
throw MyException("Something went wrong in riskyOperation!");
}
int main() {
try {
riskyOperation();
} catch (const MyException& e) {
std::cerr << "Custom exception caught: " << e.what() << "\n";
}
}
Best Practices in Modern C++ #
- Always derive from
std::exception
for consistency. - Throw by value, catch by const reference.
- Do not use exceptions for control flow.
- Use RAII and smart pointers to manage dynamic resources safely.
- Catch specific exceptions first and provide meaningful error messages.
Conclusion #
By combining try
, catch
, throw
, and RAII, modern C++ provides a robust framework for handling runtime errors. This leads to safer, more maintainable, and readable code. Use exceptions judiciously, design specific exception types, and leverage RAII to prevent resource leaks and unexpected crashes.