Bytebook Compiler

Beyond the Basics

Exception Handling

Handle errors cleanly with try, throw and catch: standard exception classes, multiple and catch-all handlers, custom exceptions, and stack unwinding.

5 min read5 runnable examples4-question quiz

Programs meet errors at run time: division by zero, a missing file, bad input, memory exhaustion. Checking return codes everywhere clutters code and errors are easy to ignore. Exceptions separate error detection from error handling.

  • throw — signal an error.
  • try — wrap code that might throw.
  • catch — handle a specific kind of error.
C++
#include <iostream>
using namespace std;

double divide(double a, double b) {
    if (b == 0) throw "Division by zero!";        // throw a value
    return a / b;
}

int main() {
    try {
        cout << divide(10, 4) << endl;
        cout << divide(1, 0) << endl;              // throws: the rest of try is skipped
        cout << "never printed" << endl;
    } catch (const char *msg) {
        cout << "Error: " << msg << endl;
    }
    cout << "Program continues normally." << endl;
    return 0;
}
Output
2.5
Error: Division by zero!
Program continues normally.

Standard exception classes

The standard library throws objects derived from std::exception. Each has a what() method returning a message.

Class (header <stdexcept>) Typical cause
invalid_argument a bad argument, e.g. stoi("abc")
out_of_range index past the end, e.g. vector::at(100)
length_error a container grown beyond its maximum
overflow_error, underflow_error arithmetic range errors
runtime_error general errors detectable only at run time
bad_alloc (<new>) new failed to get memory
C++
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    try {
        cout << v.at(1) << endl;
        cout << v.at(10) << endl;                   // .at() checks bounds and throws
    } catch (const out_of_range &e) {
        cout << "out_of_range caught" << endl;
    }

    string inputs[] = {"42", "abc", "99999999999"};
    for (const string &s : inputs) {
        try {
            int n = stoi(s);                            // may throw
            cout << "stoi(\"" << s << "\") = " << n << endl;
        } catch (const invalid_argument &) {
            cout << "\"" << s << "\" is not a number" << endl;
        } catch (const out_of_range &) {
            cout << "\"" << s << "\" is too big for an int" << endl;
        }
    }
    return 0;
}
Output
2
out_of_range caught
stoi("42") = 42
"abc" is not a number
"99999999999" is too big for an int

Multiple catch blocks and catch-all

Handlers are tried in order; the first matching one runs. Put specific types before general ones, and catch (...) (catch anything) last:

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

void risky(int option) {
    if (option == 1) throw 404;
    if (option == 2) throw runtime_error("disk not ready");
    if (option == 3) throw 3.14;
    cout << "option " << option << " ran fine" << endl;
}

int main() {
    for (int option = 0; option <= 3; option++) {
        try {
            risky(option);
        } catch (int code) {
            cout << "int error code " << code << endl;
        } catch (const exception &e) {                 // catches every std exception
            cout << "std::exception: " << e.what() << endl;
        } catch (...) {
            cout << "unknown error" << endl;
        }
    }
    return 0;
}
Output
option 0 ran fine
int error code 404
std::exception: disk not ready
unknown error

Custom exceptions

Derive from std::exception (or runtime_error) to describe your own errors:

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

class InsufficientFunds : public runtime_error {
public:
    double shortBy;
    InsufficientFunds(double s) : runtime_error("insufficient funds"), shortBy(s) {}
};

class Account {
    double balance = 1000;
public:
    void withdraw(double amt) {
        if (amt <= 0) throw invalid_argument("amount must be positive");
        if (amt > balance) throw InsufficientFunds(amt - balance);
        balance -= amt;
    }
    double getBalance() const { return balance; }
};

int main() {
    Account acc;
    double amt;
    cout << "Withdraw how much? ";
    cin >> amt;
    try {
        acc.withdraw(amt);
        cout << "Done. Balance: " << acc.getBalance() << endl;
    } catch (const InsufficientFunds &e) {
        cout << "Failed: " << e.what() << " (short by Rs " << e.shortBy << ")" << endl;
    } catch (const invalid_argument &e) {
        cout << "Failed: " << e.what() << endl;
    }
    return 0;
}
Output
Withdraw how much? 2500
Failed: insufficient funds (short by Rs 1500)
Sample input is includedTry it Yourself

Stack unwinding

When an exception propagates out of functions, every local object on the way is destroyed properly — its destructor runs. This is why C++ resources should be owned by objects (RAII): cleanup happens automatically even on errors.

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

struct Guard {
    const char *name;
    Guard(const char *n) : name(n) { cout << "acquire " << name << endl; }
    ~Guard() { cout << "release " << name << endl; }
};

void level2() { Guard g("file"); throw runtime_error("parse failed"); }
void level1() { Guard g("lock"); level2(); cout << "not reached" << endl; }

int main() {
    try { level1(); }
    catch (const exception &e) { cout << "caught: " << e.what() << endl; }
    return 0;
}
Output
acquire lock
acquire file
release file
release lock
caught: parse failed

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    What happens to the remaining statements in a try block after an exception is thrown?

  2. 2

    Where should catch (...) appear?

  3. 3

    Which exception does vector::at() throw for a bad index?

  4. 4

    During stack unwinding, what happens to local objects in the functions being exited?