Exceptions

Introduction

In C, a function reports an error with a special return value (-1, NULL) that every caller must check. C++ adds exceptions: a function that cannot do its job throws an exception, and the nearest matching catch handles it, however many functions up the call stack it is.

Return codes are easy to ignore and clutter every caller. Exceptions separate the code that finds an error from the code that deals with it, and C++ makes sure everything in between is cleaned up properly.

How it works

throw, try and catch

#include <iostream>
#include <stdexcept>
using namespace std;

double divide(double a, double b) {
    if (b == 0) throw runtime_error("division by zero");
    return a / b;
}

int main() {
    try {
        cout << divide(10, 4) << endl;
        cout << divide(1, 0) << endl;
        cout << "not printed" << endl;
    } catch (const exception& e) {
        cout << "error: " << e.what() << endl;
    }
    cout << "the program goes on" << endl;
    return 0;
}
Open in compiler
  • throw runtime_error("division by zero") creates an exception object and leaves divide at once: the return after it never runs.
  • The call sits inside try { ... }, so C++ looks for a catch that matches the exception's type. catch (const exception& e) matches any standard exception, because runtime_error is derived from exception. e.what() gives its message.
  • After the catch block, the program carries on normally. Watch the trace: the throw, the jump to the catch, and the exception object on the heap.

Catch by const &: it does not copy the exception, and it keeps the derived type for what().

Syntax

try {
    riskyWork();
} catch (const out_of_range& e) {      // the most specific first
    cout << e.what() << endl;
} catch (const exception& e) {         // anything else
    cout << "error: " << e.what() << endl;
}

Example

#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

class Guard {
    string name;
public:
    Guard(string n) : name(n) {
        cout << "enter " << name << endl;
    }
    ~Guard() {
        cout << "leave " << name << endl;
    }
};

void inner() {
    Guard g("inner");
    throw runtime_error("something failed");
}

void outer() {
    Guard g("outer");
    inner();
    cout << "not printed" << endl;
}

int main() {
    try {
        outer();
    } catch (const runtime_error& e) {
        cout << "caught: " << e.what() << endl;
    }
    return 0;
}
Open in compiler

When an exception leaves a function, its local objects are still destroyed properly: their destructors run, newest first. Here inner's Guard and outer's Guard both say goodbye before the catch runs. This stack unwinding is why RAII objects (vector, string, your own classes with destructors) never leak, even when errors happen.

Exception classThrown by / used for
runtime_errorerrors found while running
out_of_rangevector::at, string::at with a bad index
invalid_argumentstoi("abc")
exceptionthe base class of all of them: catch it last

Line by line

  1. Guard prints enter when it is built and leave when it is destroyed.
  2. inner makes a guard and then throws a runtime_error.
  3. outer makes its own guard and calls inner. The line after the call never runs.
  4. As the exception leaves inner, its guard is destroyed, and the same happens as it leaves outer.
  5. The catch in main matches runtime_error and prints the message, after both leave lines.

Expected result

enter outer
enter inner
leave inner
leave outer
caught: something failed

In the real world

Standard library functions such as vector::at and stoi report errors by throwing. Large applications usually catch exceptions near the top of each task, such as a web request, so that one failed task does not stop the whole program.

Common mistakes

  • An exception that nothing catches stops the program (terminate). StepVioz tells you which type was thrown.
  • Catching by value (catch (exception e)): the copy is sliced, and what() may say only "std::exception".
  • Putting catch (exception&) before catch (out_of_range&): the first matching catch wins, so list the specific ones first.

Best practices

  • Throw objects of standard exception types, or of classes derived from them.
  • Catch by const&.
  • Use exceptions for real errors, not for ordinary control flow.

Summary

A function throws an exception when it cannot do its job, and the nearest matching catch handles it. Stack unwinding destroys local objects on the way, so destructors still clean up, and catch blocks are tried in order.

Quick check

1. After throw inside a function, what runs next?
2. Does catch (const exception& e) catch a runtime_error?
3. During stack unwinding, local objects are:

Key takeaways

  • An exception jumps to the nearest matching catch.
  • Local objects are destroyed on the way, so RAII cleans up.
  • Catch by const reference, with the most specific type first.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.