C++ provides a rich set of features for building powerful, reliable applications, and one of its most important mechanisms is exception handling. With try, catch, and throw, C++ enables structured error handling that improves correctness, safety, and maintainability — especially when combined with RAII.
This guide takes a deep, practical look at exceptions, including how they work, when to use them, standard exception types, and best practices for writing exception-safe code.
1. Introduction to Exception Handling #
An exception represents an unexpected situation during runtime — such as divide-by-zero, invalid arguments, failed operations, or resource errors.
C++ handles these conditions using:
try: wrap code that may failthrow: signal failurecatch: handle the failure
Basic Example #
#include <iostream>
#include <stdexcept>
int main() {
try {
int a = 10, b = 0;
if (b == 0) {
throw std::runtime_error("Division by zero error");
}
int result = a / b;
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
The exception transfers control from the throw point to the nearest compatible catch block.
2. Exception Class Hierarchy #
C++ exceptions are objects — typically derived from std::exception.
Custom Exception Example #
#include <iostream>
#include <stdexcept>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "MyException occurred";
}
};
int main() {
try {
throw MyException();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
Deriving from std::exception:
- Integrates with standard handlers
- Provides a uniform interface via
.what() - Supports polymorphic catching
3. Throwing Exceptions #
You can throw exceptions from anywhere in your code.
Example #
#include <iostream>
#include <stdexcept>
void someFunction(bool errorCondition) {
if (errorCondition) {
throw std::runtime_error("Something went wrong in someFunction");
}
}
int main() {
try {
someFunction(true);
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
Thrown exceptions propagate upward until a matching catch is found.
4. RAII: Resource Acquisition Is Initialization #
RAII ensures resource safety — one of the strongest guarantees of C++ — especially during exceptions. When an exception is thrown, destructors for stack objects still run, releasing resources reliably.
RAII Example #
#include <iostream>
#include <stdexcept>
#include <cstdio>
class FileHandler {
public:
FileHandler(const char* filename) {
file = fopen(filename, "r");
if (!file) {
throw std::runtime_error("Failed to open file");
}
std::cout << "File opened successfully." << std::endl;
}
~FileHandler() {
if (file) {
fclose(file);
std::cout << "File closed successfully." << std::endl;
}
}
private:
FILE* file;
};
int main() {
try {
FileHandler file("example.txt");
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}
RAII ensures:
- Resources are released safely.
- No leaks occur, even if exceptions are thrown.
- Error paths and success paths behave consistently.
5. Standard Exception Types #
C++ provides many built-in exceptions, including:
std::runtime_errorstd::logic_errorstd::out_of_rangestd::invalid_argumentstd::bad_alloc
All derive from std::exception.
Example #
#include <iostream>
#include <stdexcept>
int main() {
try {
throw std::runtime_error("A critical runtime issue occurred");
} catch (const std::exception& e) {
std::cerr << "Caught standard exception: " << e.what() << std::endl;
}
}
Catching by reference to std::exception handles all derived standard exceptions.
6. Exceptions and Performance #
While exceptions provide safety and clarity, they are not free:
- Throwing is expensive (stack unwinding, object allocation, RTTI lookup).
- Use exceptions for errors, not normal control flow.
- For ultra-hot code paths, error codes may be preferred.
Exception presence has little cost — but actually throwing does.
7. Best Practices #
✔ Catch by reference #
Use catch (const std::exception& e).
✔ Avoid catch (...) unless necessary
#
It hides too much and makes debugging harder.
✔ Use exceptions only for exceptional cases #
Do not use exceptions for branching logic.
✔ Ensure exception-safe code #
Provide strong or basic exception guarantees:
- No leaks
- No partial updates without rollback
- RAII everywhere
✔ Never throw in a destructor (unless you really know why) #
8. Conclusion #
Exception handling is an essential part of modern C++ development. By understanding try/catch, designing custom exception types, combining exceptions with RAII, and following best practices, you can build programs that are both robust and maintainable.
C++ gives you the tools — and with proper discipline, exception handling becomes a powerful ally in writing high-quality software.