Things go wrong in every real program. A user types "ten" where a number was expected, a file can't be opened, a bank account doesn't have enough money for a withdrawal, an index points past the end of a vector. A function that detects a problem it can't fix needs a way to say "something went wrong" to whoever called it, often several functions up.
C++ uses exceptions for this. A function throws an exception object describing the problem. The normal flow stops immediately, and C++ looks back up the chain of calls for a matching catch block that knows how to handle it. If none is found, the program ends with an error.
In this lesson you'll throw and catch exceptions, use the standard exception types that the library itself throws, write your own exception class, and see how C++ cleans up objects automatically while an exception travels, which is a big part of why modern C++ code can be both safe and short.
throw, try and catch
double divide(double a, double b) {
if (b == 0) throw invalid_argument("Can't divide by zero");
return a / b;
}
try {
cout << divide(10, 0) << endl;
} catch (const invalid_argument& e) {
cout << "Error: " << e.what() << endl;
}throwcreates an exception and leaves the function immediately.- Code that might throw goes inside
try { }. catch (const Type& e)handles exceptions of that type. Catch by const reference, so the object isn't copied or cut down.e.what()returns the message the exception was created with.
The standard exception types
The standard library's exceptions are classes in <stdexcept>, all inheriting from std::exception:
| Type | Meaning | Thrown by, for example |
|---|---|---|
invalid_argument | A value doesn't make sense | stoi("ten") |
out_of_range | A value or index is outside the allowed range | v.at(99), stoi("99999999999") |
runtime_error | Something went wrong while running | Your own code: file or network problems |
logic_error | A mistake in the program's logic | Parent of invalid_argument and out_of_range |
bad_alloc | Memory couldn't be allocated | new |
Because they share a parent, catch (const exception& e) catches all of them. Note that v[99] does not throw: square brackets don't check the index, while v.at(99) does.
Several catch blocks and custom exceptions
A try can have several catch blocks. C++ checks them in order and uses the first that matches, so put specific types before general ones. catch (...) catches absolutely anything, as a last resort.
For errors specific to your program, write your own class that inherits from a standard one:
class InsufficientFunds : public runtime_error {
public:
explicit InsufficientFunds(double needed)
: runtime_error("Need " + to_string(needed) + " more") {}
};Callers can catch exactly InsufficientFunds, while code that catches exception still handles it. Inside a catch block, throw; on its own re-throws the same exception to the next level up.
Stack unwinding and RAII
When an exception leaves a function, C++ unwinds the stack: every local object in every function being left is destroyed properly, with its destructor called. That's why modern C++ relies on RAII: objects such as string, vector, ifstream and smart pointers release their memory and files in their destructors, so nothing leaks when an exception passes through.
Mark functions that will never throw with noexcept. It documents the promise and lets the library use faster code paths, for example when a vector moves its items.
C++ Exceptions Syntax
Exception syntax
#include <stdexcept>
throw runtime_error("message");
try {
risky();
} catch (const invalid_argument& e) {
cerr << e.what();
} catch (const exception& e) {
cerr << e.what();
} catch (...) {
cerr << "unknown error";
}
class MyError : public runtime_error {
public:
explicit MyError(const string& m) : runtime_error(m) {}
};C++ Exceptions Summary
throwsignals an error;trywraps risky code;catch (const Type& e)handles it.e.what()gives the message; standard exceptions live in<stdexcept>and inherit from std::exception.v.at(i)andstoithrow on bad input;v[i]doesn't check at all.- Put specific catch blocks before general ones; custom exceptions inherit from runtime_error or similar.
- Stack unwinding destroys local objects, so RAII types clean up automatically when exceptions occur.
C++ Exceptions Examples
Each example shows the code, the output it produces and what each part does.
Reading numbers safely
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
int main() {
vector<string> inputs = {"42", "ten", "99999999999", "7"};
for (const string& text : inputs) {
try {
int n = stoi(text);
cout << text << " -> " << n * 2 << endl;
} catch (const invalid_argument& e) {
cout << text << " -> not a number" << endl;
} catch (const out_of_range& e) {
cout << text << " -> too big for an int" << endl;
}
}
return 0;
}42 -> 84
ten -> not a number
99999999999 -> too big for an int
7 -> 14Line-by-line explanation
- Line 11stoi throws if the text isn't a valid int.
- Lines 13 to 14"ten" can't be converted at all, so invalid_argument is thrown.
- Lines 15 to 1699999999999 is a number, but larger than an int can hold.
- Lines 9 to 18The loop carries on after each error, because every exception is caught inside it.
A custom exception for a bank account
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
class InsufficientFunds : public runtime_error {
public:
explicit InsufficientFunds(int shortBy)
: runtime_error("Insufficient funds: short by " + to_string(shortBy)) {}
};
class Account {
int balance = 0;
public:
void deposit(int amount) {
if (amount <= 0) throw invalid_argument("Deposit must be positive");
balance += amount;
}
void withdraw(int amount) {
if (amount > balance) throw InsufficientFunds(amount - balance);
balance -= amount;
}
int getBalance() const { return balance; }
};
int main() {
Account acc;
acc.deposit(100);
try {
acc.withdraw(30);
acc.withdraw(120);
cout << "This line is skipped" << endl;
} catch (const InsufficientFunds& e) {
cout << e.what() << endl;
}
try {
acc.deposit(-5);
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
}
cout << "Balance: " << acc.getBalance() << endl;
return 0;
}Insufficient funds: short by 50
Error: Deposit must be positive
Balance: 70Line-by-line explanation
- Lines 6 to 10A custom exception that builds a helpful message.
- Line 20Withdrawing more than the balance throws, and the balance isn't changed.
- Lines 31 to 32The throw jumps straight to the catch block, so the line after it never runs.
- Line 38Catching std::exception handles invalid_argument too, because it's a child class.
Cleanup during stack unwinding
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
struct Tracker {
string name;
Tracker(string n) : name(n) { cout << "open " << name << endl; }
~Tracker() { cout << "close " << name << endl; }
};
void inner() {
Tracker t("inner file");
throw runtime_error("disk full");
}
void outer() {
Tracker t("outer file");
inner();
cout << "never printed" << endl;
}
int main() {
try {
outer();
} catch (const exception& e) {
cout << "caught: " << e.what() << endl;
}
return 0;
}open outer file
open inner file
close inner file
close outer file
caught: disk fullLine-by-line explanation
- Lines 6 to 10A small class that prints when it's created and destroyed, like a file or lock would release itself.
- Line 14The exception leaves inner, then outer. On the way, both Tracker objects are destroyed in reverse order before the catch block runs. Nothing is left open.
Common C++ Exceptions Mistakes
Catching by value
catch (exception e) { ... } // copies and slices the objectcatch (const exception& e) { ... }General catch before specific
catch (const exception& e) { ... }
catch (const invalid_argument& e) { ... } // never reachedcatch (const invalid_argument& e) { ... }
catch (const exception& e) { ... }Expecting [] to check the index
vector<int> v = {1, 2, 3};
try { cout << v[10]; } catch (...) { } // no exception: undefined behaviourtry { cout << v.at(10); } catch (const out_of_range& e) { cout << e.what(); }C++ Exceptions in Real Projects
Exceptions in C++ projects
Applications use exceptions to report failures such as unreadable configuration files, invalid user input and lost network connections, catching them near the top of the program to show a clear message or log the problem. Libraries like the standard library, Boost and many database drivers throw them. Some fields, such as games and embedded systems, switch exceptions off for speed or size and use return codes instead, so you'll see both styles.
C++ Exceptions FAQ
What happens if an exception is never caught?
If an exception leaves main without being caught, C++ calls std::terminate, which ends the program straight away. With g++ you'll see a message such as "terminate called after throwing an instance of 'std::runtime_error'" followed by the what() text. Whether local objects are cleaned up first isn't guaranteed, so catch exceptions at least in main if cleanup matters.
Can I throw an exception from a destructor?
You shouldn't. Destructors are noexcept by default, so an exception escaping one calls std::terminate, and g++ even warns "'throw' will always call 'terminate'". If cleanup can fail, catch the error inside the destructor, or give the class a separate function such as close() that callers can use when they want to handle the error.
What happens if a constructor throws an exception?
The object is never considered created, so its destructor does not run. Members and base classes that were already fully built are destroyed properly, which is another reason to hold resources in members such as vector, string and unique_ptr rather than raw pointers. Throwing is the standard way for a constructor to report that it couldn't build a valid object.
Can I throw an int or a string in C++?
Yes, C++ lets you throw almost any type: throw 42; is caught by catch (int code). It's poor practice, though, because the value carries no type information that callers can catch selectively and it doesn't work with catch (const exception& e). Throw standard exception types or your own classes derived from them.
Are exceptions slow in C++?
On the main compilers, code that doesn't throw runs at essentially full speed, because the error-handling information is kept aside until it's needed. Actually throwing and catching is much slower than returning a value, often taking microseconds. That's fine for genuine errors but a bad fit for something that happens thousands of times a second.
C++ Exceptions Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the code so it throws an error for a negative age.
if (age < 0) invalid_argument("Age can't be negative");
How should exceptions usually be caught?
What does this print?
try {
cout << "A";
throw runtime_error("x");
cout << "B";
} catch (const exception& e) {
cout << "C";
}
cout << "D" << endl;
Which call throws out_of_range for an invalid index?
What happens to local objects when an exception leaves a function?
C++ Exceptions Quiz
5 questions to check what you learned.
Which keyword signals an error?
- catch
- throw
- try
- error
What does
e.what()return?- The line number
- The exception's type
- The exception's message
- true or false
What does
stoi("hello")throw?- invalid_argument
- out_of_range
- runtime_error
- Nothing, it returns 0
What does
catch (...)catch?- Only standard exceptions
- Nothing
- Only runtime_error
- Any exception of any type
When is returning a value better than throwing?
- Always
- When the outcome is expected and common, such as "not found"
- When the error is serious
- Never