Exception Handling in C++
Exception handling is C++'s mechanism for dealing with errors that you cannot handle right where they happen. You throw an exception when something goes wrong, and a matching catch block elsewhere takes over, skipping the rest of the failed work. It separates the normal, happy-path code from the error-handling code, so a function can report "this failed" without polluting every line with checks.
Here is the basic shape.
try, throw, catch
Wrap risky code in a try block and handle problems in catch.
#include <iostream>
#include <stdexcept>
int divide(int a, int b) {
if (b == 0) {
throw std::invalid_argument("division by zero");
}
return a / b;
}
int main() {
try {
std::cout << divide(10, 2) << "\n"; // 5
std::cout << divide(10, 0) << "\n"; // throws
std::cout << "this line never runs\n";
} catch (const std::invalid_argument& e) {
std::cout << "Error: " << e.what() << "\n";
}
}
Output:
5
Error: division by zero
When divide(10, 0) throws, execution jumps straight to the matching catch, skipping the unreached line. e.what() returns the message you passed.
Catch by const reference
Always catch exceptions by const reference (const std::exception&). Catching by value slices polymorphic exception objects and makes a needless copy; catching by reference preserves the real type.
Use the standard exception hierarchy
The <stdexcept> header gives ready-made exception types, all derived from std::exception:
std::invalid_argument,std::out_of_range,std::length_error(logic errors)std::runtime_error,std::overflow_error(runtime errors)
Catching const std::exception& handles all of them, because they share that base class.
#include <iostream>
#include <vector>
#include <stdexcept>
int main() {
std::vector<int> v{1, 2, 3};
try {
v.at(10) = 5; // at() throws std::out_of_range
} catch (const std::exception& e) { // base class catches all
std::cout << "Caught: " << e.what() << "\n";
}
}
Multiple catch blocks
You can list several catch blocks; the first matching type wins. Order them from most specific to most general.
try {
// ...
} catch (const std::out_of_range& e) {
// handle this specific case
} catch (const std::exception& e) {
// fallback for anything else derived from std::exception
}
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesException safety and RAII
The real power of exceptions in C++ comes from RAII: objects clean themselves up in their destructors, which run automatically even when an exception unwinds the stack. A std::unique_ptr, std::vector, or file wrapper releases its resource no matter how the block is exited.
#include <memory>
void process() {
auto data = std::make_unique<int[]>(1000); // owns memory
riskyStep(); // if this throws, data is STILL freed automatically
} // unique_ptr's destructor runs during unwinding
This is why you should prefer RAII types over manual new/delete: with raw pointers, an exception between new and delete leaks memory; with smart pointers it cannot.
Defining your own exception type
For application-specific errors, derive from std::exception (or one of its subclasses) so callers can catch it through the base class. Override what() to return a message.
#include <stdexcept>
#include <string>
class FeeError : public std::runtime_error {
public:
explicit FeeError(const std::string& msg)
: std::runtime_error(msg) {} // base stores the message
};
// thrown like any other: throw FeeError("amount exceeds balance");
// caught by: catch (const std::exception& e) { ... e.what() ... }
Because FeeError inherits from std::runtime_error (itself a std::exception), existing catch (const std::exception&) handlers pick it up automatically, while code that wants to handle it specifically can write catch (const FeeError&).
Common mistakes
- Catching by value.
catch (std::exception e)slices derived exceptions. Catch byconst&. - Throwing from a destructor. If a destructor throws during stack unwinding, the program terminates. Destructors should not throw.
- Using exceptions for ordinary control flow. Exceptions are for exceptional, error conditions — not for normal branching, which is far cheaper with
if. - Leaking resources on the throw path. Manual
newwithout RAII leaks if an exception fires beforedelete. Use smart pointers and containers.
FAQ
Should I catch every exception with catch (...)?
catch (...) catches anything but gives you no information about what happened. Prefer catching const std::exception& so you can read what(); reserve catch (...) for top-level safety nets.
Are exceptions slow? Throwing is relatively expensive, but the non-throwing path costs essentially nothing in modern compilers. So exceptions are fine for genuine errors and a poor fit for routine, frequent branching.
Keep learning
- See RAII in action with object lifetimes in Constructors & Destructors in C++.
- Containers like vectors throw
std::out_of_range— see Vectors in C++. - Browse the full series on the C++ OOP hub.
Write robust, error-resilient C++ with mentor guidance in Jalgaon — join the waitlist for our C++ Programming course.
Want to learn this properly?
Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.
Browse coursesFounder, Infoplanet
Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.
Related guides
Classes & Objects in C++
Understand C++ classes and objects — defining a class, creating objects, member functions, and access control with public and private members.
Constructors & Destructors in C++
Understand C++ constructors and destructors — how objects are initialised and cleaned up, including parameterised, copy constructors, and initializer lists.
Encapsulation & Abstraction in C++
Understand encapsulation and abstraction in C++ — hiding internal data, exposing controlled interfaces, and why both make code safer and easier to change.
