08 Exception Handling & RAII
Overview: Exception Safety and Lifetime-bound Cleanup
C++ uses Stack Unwinding and RAII to handle errors. By tying system resources to the lifetime of stack objects, you can build exception-safe programs that prevent memory leaks.
1. Exception Handling & Stack Unwinding
C++ uses try, catch, and throw to separate error handling from business logic.
try {
throw std::runtime_error("Device error");
} catch (const std::exception& ex) {
std::cout << ex.what(); // Catch by const reference
}The Stack Unwinding Process:
When an exception is thrown:
- The CPU immediately halts execution of the current function.
- The runtime walks backwards up the call stack (unwinding) to locate a matching
catchblock. - During this search, all local stack-allocated variables in the exited stack frames are destructed.
- If no matching handler is found, the program terminates immediately via
std::terminate.
Best Practices:
- Never throw on heap: Do not throw pointers (
throw new MyException()). This requires manual memory management at the catch site, risking leaks. Throw by value and catch by const reference to prevent object slicing. - Never throw in destructors: If an exception is thrown while another exception is unwinding the stack, the runtime terminates immediately. Keep destructors
noexcept.
2. The RAII Paradigm
Resource Acquisition Is Initialization (RAII) is C++‘s most important design pattern. It links the allocation and release of resources to the lifecycle of a stack-allocated object.
Stack Object Initialized (Constructor) ---> Resource Acquired (file, memory, lock)
... Object performs work ...
Stack Object Leaves Scope (Destructor) ---> Resource Released (closed, deleted, unlocked)
Why RAII is preferred over C-style checks:
Consider error cleanup in C, which requires nested if statements or goto blocks:
FILE* f = fopen("data.txt", "r");
if (!f) return error_code;
if (!doStep1(f)) { fclose(f); return error_code; }
if (!doStep2(f)) { fclose(f); return error_code; }
fclose(f);In C++, std::ifstream uses RAII to close the file handle automatically in its destructor:
void readData() {
std::ifstream file("data.txt"); // Constructor opens file
doStep1(file);
doStep2(file);
} // Destructor automatically closes file, even if an exception is thrown💻 Conceptual Code Demonstration
#include <iostream>
#include <fstream>
#include <stdexcept>
// A custom class implementing RAII for raw heap allocation
class IntegerArrayBuffer {
int* buffer;
public:
IntegerArrayBuffer(size_t size) : buffer(new int[size]) {
std::cout << "Buffer allocated on Heap.\n";
}
~IntegerArrayBuffer() {
delete[] buffer;
std::cout << "Buffer automatically deleted from Heap.\n";
}
};
void runCalculations() {
// Heap allocation wrapped in a stack-bound RAII object
IntegerArrayBuffer myBuffer(100);
// Trigger an error
std::cout << "Throwing exception...\n";
throw std::runtime_error("Calculation failed");
} // myBuffer's destructor would run here during normal exit
int main() {
try {
runCalculations();
}
catch (const std::exception& ex) {
// During stack unwinding, myBuffer's destructor is run
// BEFORE execution enters this catch block. No memory is leaked.
std::cout << "Caught exception: " << ex.what() << '\n';
}
return 0;
}