C++ Exceptions

Handle errors with try, catch and throw, use the standard exception types, write your own exception class, and understand how RAII keeps programs safe when errors happen.

Lesson 16 of 44Intermediate18 min read

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

C++
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;
}
  • throw creates 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:

TypeMeaningThrown by, for example
invalid_argumentA value doesn't make sensestoi("ten")
out_of_rangeA value or index is outside the allowed rangev.at(99), stoi("99999999999")
runtime_errorSomething went wrong while runningYour own code: file or network problems
logic_errorA mistake in the program's logicParent of invalid_argument and out_of_range
bad_allocMemory couldn't be allocatednew

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:

C++
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

  • throw signals an error; try wraps 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) and stoi throw 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.

Example 1

Reading numbers safely

C++
#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;
}
Output
42 -> 84
ten -> not a number
99999999999 -> too big for an int
7 -> 14
Line-by-line explanation
  1. Line 11stoi throws if the text isn't a valid int.
  2. Lines 13 to 14"ten" can't be converted at all, so invalid_argument is thrown.
  3. Lines 15 to 1699999999999 is a number, but larger than an int can hold.
  4. Lines 9 to 18The loop carries on after each error, because every exception is caught inside it.
Example 2

A custom exception for a bank account

C++
#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;
}
Output
Insufficient funds: short by 50
Error: Deposit must be positive
Balance: 70
Line-by-line explanation
  1. Lines 6 to 10A custom exception that builds a helpful message.
  2. Line 20Withdrawing more than the balance throws, and the balance isn't changed.
  3. Lines 31 to 32The throw jumps straight to the catch block, so the line after it never runs.
  4. Line 38Catching std::exception handles invalid_argument too, because it's a child class.
Example 3

Cleanup during stack unwinding

C++
#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;
}
Output
open outer file
open inner file
close inner file
close outer file
caught: disk full
Line-by-line explanation
  1. Lines 6 to 10A small class that prints when it's created and destroyed, like a file or lock would release itself.
  2. 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

Don't
catch (exception e) { ... }          // copies and slices the object
Do
catch (const exception& e) { ... }

General catch before specific

Don't
catch (const exception& e) { ... }
catch (const invalid_argument& e) { ... }   // never reached
Do
catch (const invalid_argument& e) { ... }
catch (const exception& e) { ... }

Expecting [] to check the index

Don't
vector<int> v = {1, 2, 3};
try { cout << v[10]; } catch (...) { }   // no exception: undefined behaviour
Do
try { 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.

Fill in the blankseasy

Complete the code so it throws an error for a negative age.

if (age < 0)  invalid_argument("Age can't be negative");
Multiple choiceeasy

How should exceptions usually be caught?

Predict the outputmedium

What does this print?

try {
    cout << "A";
    throw runtime_error("x");
    cout << "B";
} catch (const exception& e) {
    cout << "C";
}
cout << "D" << endl;
Multiple choicemedium

Which call throws out_of_range for an invalid index?

Multiple choicehard

What happens to local objects when an exception leaves a function?

C++ Exceptions Quiz

5 questions to check what you learned.

  1. Which keyword signals an error?

    • catch
    • throw
    • try
    • error
  2. What does e.what() return?

    • The line number
    • The exception's type
    • The exception's message
    • true or false
  3. What does stoi("hello") throw?

    • invalid_argument
    • out_of_range
    • runtime_error
    • Nothing, it returns 0
  4. What does catch (...) catch?

    • Only standard exceptions
    • Nothing
    • Only runtime_error
    • Any exception of any type
  5. 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