Exception Handling in C++

    Atul Kabra4 min readUpdated

    Exception handling is C++'s mechanism for dealing with errors that you cannot handle right where they happen. You throw an exception when something goes wrong, and a matching catch block elsewhere takes over, skipping the rest of the failed work. It separates the normal, happy-path code from the error-handling code, so a function can report "this failed" without polluting every line with checks.

    Here is the basic shape.

    try, throw, catch

    Wrap risky code in a try block and handle problems in catch.

    #include <iostream>
    #include <stdexcept>
    
    int divide(int a, int b) {
        if (b == 0) {
            throw std::invalid_argument("division by zero");
        }
        return a / b;
    }
    
    int main() {
        try {
            std::cout << divide(10, 2) << "\n";   // 5
            std::cout << divide(10, 0) << "\n";   // throws
            std::cout << "this line never runs\n";
        } catch (const std::invalid_argument& e) {
            std::cout << "Error: " << e.what() << "\n";
        }
    }
    

    Output:

    5
    Error: division by zero
    

    When divide(10, 0) throws, execution jumps straight to the matching catch, skipping the unreached line. e.what() returns the message you passed.

    Catch by const reference

    Always catch exceptions by const reference (const std::exception&). Catching by value slices polymorphic exception objects and makes a needless copy; catching by reference preserves the real type.

    Use the standard exception hierarchy

    The <stdexcept> header gives ready-made exception types, all derived from std::exception:

    • std::invalid_argument, std::out_of_range, std::length_error (logic errors)
    • std::runtime_error, std::overflow_error (runtime errors)

    Catching const std::exception& handles all of them, because they share that base class.

    #include <iostream>
    #include <vector>
    #include <stdexcept>
    
    int main() {
        std::vector<int> v{1, 2, 3};
        try {
            v.at(10) = 5;     // at() throws std::out_of_range
        } catch (const std::exception& e) {   // base class catches all
            std::cout << "Caught: " << e.what() << "\n";
        }
    }
    

    Multiple catch blocks

    You can list several catch blocks; the first matching type wins. Order them from most specific to most general.

    try {
        // ...
    } catch (const std::out_of_range& e) {
        // handle this specific case
    } catch (const std::exception& e) {
        // fallback for anything else derived from std::exception
    }
    

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses

    Exception safety and RAII

    The real power of exceptions in C++ comes from RAII: objects clean themselves up in their destructors, which run automatically even when an exception unwinds the stack. A std::unique_ptr, std::vector, or file wrapper releases its resource no matter how the block is exited.

    #include <memory>
    
    void process() {
        auto data = std::make_unique<int[]>(1000);  // owns memory
        riskyStep();   // if this throws, data is STILL freed automatically
    }                  // unique_ptr's destructor runs during unwinding
    

    This is why you should prefer RAII types over manual new/delete: with raw pointers, an exception between new and delete leaks memory; with smart pointers it cannot.

    Defining your own exception type

    For application-specific errors, derive from std::exception (or one of its subclasses) so callers can catch it through the base class. Override what() to return a message.

    #include <stdexcept>
    #include <string>
    
    class FeeError : public std::runtime_error {
    public:
        explicit FeeError(const std::string& msg)
            : std::runtime_error(msg) {}   // base stores the message
    };
    
    // thrown like any other: throw FeeError("amount exceeds balance");
    // caught by:  catch (const std::exception& e) { ... e.what() ... }
    

    Because FeeError inherits from std::runtime_error (itself a std::exception), existing catch (const std::exception&) handlers pick it up automatically, while code that wants to handle it specifically can write catch (const FeeError&).

    Common mistakes

    • Catching by value. catch (std::exception e) slices derived exceptions. Catch by const&.
    • Throwing from a destructor. If a destructor throws during stack unwinding, the program terminates. Destructors should not throw.
    • Using exceptions for ordinary control flow. Exceptions are for exceptional, error conditions — not for normal branching, which is far cheaper with if.
    • Leaking resources on the throw path. Manual new without RAII leaks if an exception fires before delete. Use smart pointers and containers.

    FAQ

    Should I catch every exception with catch (...)? catch (...) catches anything but gives you no information about what happened. Prefer catching const std::exception& so you can read what(); reserve catch (...) for top-level safety nets.

    Are exceptions slow? Throwing is relatively expensive, but the non-throwing path costs essentially nothing in modern compilers. So exceptions are fine for genuine errors and a poor fit for routine, frequent branching.

    Keep learning

    Write robust, error-resilient C++ with mentor guidance in Jalgaon — join the waitlist for our C++ Programming course.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses
    Atul Kabra

    Founder, Infoplanet

    Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.

    Related guides