C++ Exceptions

C++ exceptions are a way to report errors that a function cannot handle by itself. An exception interrupts the normal flow of code and looks for a matching catch block that can handle the problem.

Exceptions are useful when returning an ordinary value is not enough to describe failure. They let the code that detects an error stay separate from the code that decides how to recover.

The Basic Idea

Exception handling uses three main keywords:

  • throw creates and sends an exception.
  • try marks a block of code where an exception might happen.
  • catch handles an exception that matches its parameter type.

Many standard library errors inherit from std::exception, which provides the what() member function. That function returns a short message describing the error.

Example: Throwing And Catching An Exception

This program calculates the average score per student. Dividing by zero would be an error, so the helper function throws std::invalid_argument when the student count is invalid.

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

class StepLog {
private:
    std::string name;

public:
    StepLog(std::string stepName) : name(stepName) {
        std::cout << "Start " << name << std::endl;
    }

    ~StepLog() {
        std::cout << "End " << name << std::endl;
    }
};

double averageScore(int totalScore, int students) {
    StepLog log("averageScore");

    if (students == 0) {
        throw std::invalid_argument("students must not be zero");
    }

    return static_cast<double>(totalScore) / students;
}

int main(void) {
    try {
        double result = averageScore(90, 0);
        std::cout << "Average: " << result << std::endl;
    } catch (const std::exception& error) {
        std::cout << "Caught error: " << error.what() << std::endl;
    }

    std::cout << "Program continues" << std::endl;

    return 0;
}

Output:

Start averageScore
End averageScore
Caught error: students must not be zero
Program continues

How The Example Works

main() calls averageScore(90, 0) inside a try block. Inside averageScore(), the students value is checked before division. Because it is zero, the function throws a std::invalid_argument object.

When the exception is thrown, averageScore() stops immediately. The return statement is skipped, and control searches for a matching catch block. The handler catch (const std::exception& error) matches because std::invalid_argument is a kind of std::exception.

The StepLog object also shows an important C++ rule. Its destructor runs before the exception reaches main(). This process is called stack unwinding: as C++ leaves each scope because of an exception, it destroys local objects correctly.

Catch By Reference

Usually catch standard exceptions as const std::exception&. The reference avoids copying the exception object, and const says the handler will not modify it.

You can also catch more specific exception types first, then more general types later. C++ checks catch blocks from top to bottom, so put the most specific handlers before catch (const std::exception&).

When To Use Exceptions

Use exceptions for errors that prevent a function from doing its job, such as invalid input, failed parsing, missing files, or impossible program states. Do not use exceptions for normal loop control or ordinary choices that happen often.

A good throwing function should leave objects in a valid state. Prefer RAII types such as std::string, std::vector, smart pointers, and file stream objects so cleanup still happens when exceptions are thrown.

Guidelines

  • Throw standard exception types such as std::invalid_argument, std::runtime_error, or your own exception classes when needed.
  • Catch exceptions at a level that can report the error, recover, or safely stop the operation.
  • Catch by const reference, not by value.
  • Keep destructors simple and avoid throwing exceptions from destructors.
  • Do not ignore an exception unless you truly know the program can continue correctly.

Takeaway: exceptions separate error reporting from error handling, while C++ object lifetimes still protect cleanup as control leaves a scope.