Skip to content

Expert · Exceptions, concurrency and building projects with CMake · topic 34 of 42 · 30 min

Exceptions: throw, try, catch and noexcept

Explanation

An exception reports an error that the current function cannot handle: you throw it, and the nearest matching catch block up the call stack handles it. Local objects are destroyed on the way (stack unwinding), so RAII keeps resources safe. Throw standard exception types by value and catch them by const reference.

Why exceptions?

Errors happen: a file is missing, a network call times out, a user types letters for an amount. Returning error codes means every caller must check every result, and it is easy to forget. An exception separates error handling from normal code: the function that detects a problem throws, and a function higher up that knows what to do catches. Constructors, which have no return value, can only report failure this way.

cpp
#include <iostream>
#include <stdexcept>
#include <string>

double withdraw(double balance, double amount) {
    if (amount <= 0) throw std::invalid_argument("amount must be positive");
    if (amount > balance) throw std::runtime_error("insufficient balance");
    return balance - amount;
}

int main() {
    try {
        double left = withdraw(500, 800);
        std::cout << "Left: " << left << '\n';        // skipped
    } catch (const std::invalid_argument& e) {
        std::cout << "Bad input: " << e.what() << '\n';
    } catch (const std::exception& e) {               // any other standard exception
        std::cout << "Error: " << e.what() << '\n';   // Error: insufficient balance
    }
}
When withdraw throws, the rest of the try block is skipped and the first catch whose type matches runs.

How throw and catch work

  1. throw creates an exception object and starts searching for a handler.
  2. The current function stops. Its local objects are destroyed in reverse order (stack unwinding), so RAII destructors run.
  3. The search moves up to the caller, then its caller, until it finds a try block with a matching catch.
  4. The catch block runs. The program continues after that try/catch.
  5. If no catch matches anywhere, std::terminate is called and the program ends.
Think of it like this: An exception is like a fire alarm in an office. The person who sees smoke pulls the alarm (throw) instead of fighting the fire alone. Everyone on the way out switches off what they were using (destructors run). The fire brigade at the gate (catch) handles it. If no brigade comes, the building is closed (terminate).

The standard exception family

Type (in <stdexcept> unless noted)Typical use
std::exception (<exception>)base class of all standard exceptions; has what()
std::logic_error, std::invalid_argument, std::out_of_rangebugs or bad arguments: v.at(99), std::stoi("abc")
std::runtime_errorproblems only visible at run time: a file is missing, a server failed
std::bad_alloc (<new>)new could not get memory
cpp
class PaymentError : public std::runtime_error {
public:
    PaymentError(const std::string& msg, int code)
        : std::runtime_error(msg), code_(code) {}
    int code() const noexcept { return code_; }
private:
    int code_;
};

try {
    throw PaymentError("gateway timeout", 504);
} catch (const PaymentError& e) {
    std::cout << e.what() << " (" << e.code() << ")\n";
}
Derive your own exceptions from a standard one so that catch (const std::exception&) still catches them.

Rules of good exception use

  • Throw by value (throw std::runtime_error("...")), catch by const reference (catch (const std::exception& e)). Catching by value slices derived exceptions.
  • Order catch blocks from most specific to most general; the first match wins.
  • catch (...) catches anything; use it only to log and rethrow (throw;) or at the top of main.
  • Use exceptions for exceptional situations, not normal control flow such as "end of loop".
  • Never let an exception escape a destructor.

noexcept

Marking a function noexcept promises it will not throw. If it throws anyway, std::terminate is called. Destructors are noexcept by default. Mark move constructors, move assignments and swap functions noexcept: as you saw, std::vector uses moves only when they are noexcept.

Careful: Exceptions are not the only error style. Some projects (many games, embedded systems and Google's C++ style) turn exceptions off and use error codes or types like std::optional and C++23 std::expected. Follow your project's rules; in your own projects, exceptions plus RAII are the standard C++ approach.
Tip: Wrap the body of main in try { ... } catch (const std::exception& e) { std::cerr << e.what() << '\n'; return 1; } so unexpected errors print a message instead of a bare crash.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Exceptions: throw, try, catch and noexcept · 1/6

Exceptions

Exceptions report errors that the current function cannot handle.

Prefer a video by a teacher? Find free videos on “c++ exceptions try catch throw noexcept stack unwinding tutorial” on YouTube ↗

In real life: Reading a configuration file at startup

A server reads settings like the port number from a file when it starts. If the file is missing or the port is not a number, it is pointless to continue. The loader throws, main catches the exception, prints a clear message for the operations team, and exits with a non-zero code so the deployment system notices.

cpp
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>

int loadPort(const std::string& path) {
    std::ifstream in(path);
    if (!in) throw std::runtime_error("cannot open " + path);
    std::string line;
    std::getline(in, line);
    int port = std::stoi(line);                       // may throw std::invalid_argument
    if (port < 1 || port > 65535) throw std::out_of_range("port out of range");
    return port;
}

int main() {
    try {
        std::cout << "Listening on " << loadPort("server.conf") << '\n';
    } catch (const std::exception& e) {
        std::cerr << "Startup failed: " << e.what() << '\n';
        return 1;
    }
}
One handler in main covers every failure; the ifstream closes itself during unwinding.

Practice (0/4 done)

Task 1 · Question

What prints? try { throw std::runtime_error("x"); std::cout << "A"; } catch (const std::exception&) { std::cout << "B"; } std::cout << "C";

Task 2 · Question

Which catch order is correct?

Task 3 · Question

What happens if an exception is thrown and nothing catches it?

Task 4 · Practice

Write int parseAge(const std::string& s) that throws std::invalid_argument for non-numbers (let std::stoi do it) and std::out_of_range with your own message for ages outside 0–130. In main, read ages in a loop, catch each kind separately, and print a friendly message without stopping the loop.

Quick quiz

Question 1

1.What is stack unwinding?

Question 2

2.Why catch by const reference instead of by value?

Question 3

3.A noexcept function throws. What happens?

Question 4

4.Which base class should your own exception types usually derive from?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is stack unwinding?

Answer: Destroying local objects in each function as an exception travels up to a handler

It is what makes RAII cleanup run during exceptions.

Why catch by const reference instead of by value?

Answer: It avoids copying and avoids slicing derived exception types

Catching by value copies and slices.

A noexcept function throws. What happens?

Answer: std::terminate is called

Breaking a noexcept promise ends the program.

Which base class should your own exception types usually derive from?

Answer: std::exception (often via std::runtime_error)

Then generic handlers for std::exception catch them too, and what() works.

What happens to an exception that nobody catches?Related

Answer: The program crashes and shows the error

Unhandled exceptions end the process.

From Exceptions: try, catch, finally and custom exceptions

What happens to an exception nobody catches in main?Related

Answer: The thread ends and the stack trace is printed

Uncaught exceptions end the thread.

From Exceptions: try, catch and finally

Why throw new Error("…") instead of throw "…"?Related

Answer: Error objects carry a name and a stack trace

Strings can be thrown but have no stack, which makes debugging hard.

From Error handling: try, catch, finally and throw

Why should move constructors be noexcept?Related

Answer: So std::vector moves elements when it grows instead of copying them

vector only uses a move if it cannot throw, to keep its exception guarantee.

From Move semantics: steal instead of copy

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Exceptions, concurrency and building projects with CMake” before its exam: Concurrency 1: threads, mutexes and data races, Concurrency 2: atomics, async, futures and condition variables, Building real projects: CMake, warnings, debuggers and sanitizers.
  2. Module 9 exam: Exceptions, concurrency and CMake · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Competitive programming, interviews and careers. 42 topics left in the course before the final exam.

← Previous: C++20 ranges, views and other C++20 essentials