Advanced Error Handling in C++: From try/catch to Industrial Practices
Error handling is one of the cornerstones of industrial programming. In C++, the developer has access not only to the classic try/catch mechanism, but also to a whole arsenal of advanced techniques: from RAII to exception safety guarantees. In this article, we will explore how to write robust C++ code that correctly handles failures, does not leak resources, and remains readable.
1. Basics and Common Pitfalls of try/catch
The basic syntax is familiar to everyone:
#include <iostream>#include <stdexcept>
void dangerousFunction(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // ... work with value}
int main() { try { dangerousFunction(-5); } catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Generic error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown exception caught" << std::endl; } return 0;}Important points:
- Catch by reference —
catch (const std::exception& e)avoids object slicing. - The order of catch blocks matters: first more specific exceptions, then general ones.
- Do not overuse
catch (...)— it hides all errors, including critical ones (e.g.,std::bad_alloc). - Never throw exceptions from destructors — this leads to undefined behavior (usually abnormal termination).
2. RAII as an Alternative to try/catch for Resource Management
RAII (Resource Acquisition Is Initialization) is a key C++ pattern that makes exceptions safe. The idea: a resource is acquired in the constructor and released in the destructor. If an exception occurs, the destructors of all created objects are called automatically (stack unwinding).
#include <fstream>#include <vector>#include <stdexcept>
void processFile(const std::string& filename) { std::ifstream file(filename); // RAII: file opens in the constructor if (!file.is_open()) { throw std::runtime_error("Cannot open file: " + filename); }
std::vector<int> data; int value; while (file >> value) { data.push_back(value); } // The file destructor will close the file automatically, // even if an exception occurs (e.g., bad_alloc during push_back)}Advantages of RAII over manual try/catch:
- No need to write
finallyblocks — destructors guarantee cleanup. - Code becomes more linear and readable.
- Exceptions do not lead to leaks of memory, file descriptors, etc.
3. Exception Safety Guarantees
In C++, it is common to distinguish three levels of guarantees for functions:
- No-throw guarantee — the function never throws exceptions. Marked with
noexcept. - Strong guarantee — if an exception occurs, the program state remains unchanged (atomic operation).
- Basic guarantee — upon an exception, no resources are leaked, objects remain in a consistent state, but their value may change.
Example with strong guarantee (copy-and-swap idiom):
#include <algorithm> // for std::swap#include <vector>#include <stdexcept>
class SafeVector { std::vector<int> data_;public: void push_back(int value) { // 1. Create a copy