C++ Exception Handling


Introduction

In C++, exception handling allows you to deal with runtime errors (exceptions) in a structured way, without crashing the program. Instead of checking for errors at every step, you can use exception handling to separate error-handling code from normal code flow, making the program more readable and maintainable.

C++ provides a set of keywords—try, catch, and throw—to handle exceptions.

  • try block: Contains the code that might throw an exception.
  • throw statement: Used to signal an exception.
  • catch block: Used to catch the exception thrown and handle it.

Basic Syntax of C++ Exception Handling

try {
    // Code that might throw an exception
} catch (exceptionType1 e1) {
    // Handle exception of type exceptionType1
} catch (exceptionType2 e2) {
    // Handle exception of type exceptionType2
} catch (...) {
    // Catch any other types of exceptions
}
  1. try block: This is where you write the code that might throw an exception.
  2. catch block: After the try block, you define one or more catch blocks to catch specific types of exceptions.
  3. throw statement: This is used to throw an exception from the code, which is then caught by the corresponding catch block.

Example 1: Basic Exception Handling

In this example, we'll throw and catch an exception when dividing by zero.

#include <iostream>
#include <stdexcept> // For std::runtime_error

int divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("Division by zero error!");
    }
    return a / b;
}

int main() {
    try {
        int result = divide(10, 0); // Will throw an exception
        std::cout << "Result: " << result << std::endl;
    } catch (const std::runtime_error& e) {
        std::cout << "Caught an exception: " << e.what() << std::endl;
    }

    return 0;
}

Output:

Caught an exception: Division by zero error!

In this example:

  • The function divide() checks if b is zero and throws a runtime_error exception if true.
  • The exception is caught in the catch block and the error message is displayed.

Example 2: Catching Different Types of Exceptions

C++ allows you to catch different types of exceptions separately. This helps in handling each error in a specific manner.

#include <iostream>
#include <stdexcept> // For std::invalid_argument and std::out_of_range

void exampleFunction(int x) {
    if (x < 0) {
        throw std::invalid_argument("Negative number error!");
    }
    if (x > 100) {
        throw std::out_of_range("Number out of range error!");
    }
}

int main() {
    try {
        exampleFunction(150); // This will throw std::out_of_range
    } catch (const std::invalid_argument& e) {
        std::cout << "Caught invalid argument: " << e.what() << std::endl;
    } catch (const std::out_of_range& e) {
        std::cout << "Caught out of range exception: " << e.what() << std::endl;
    } catch (...) {
        std::cout << "Caught an unknown exception!" << std::endl;
    }

    return 0;
}

Output:

Caught out of range exception: Number out of range error!

In this example:

  • The function exampleFunction() checks if the input is negative or exceeds 100, and throws different types of exceptions (invalid_argument or out_of_range).
  • The catch blocks handle each type of exception separately, allowing more precise error handling.

Example 3: Throwing Custom Exceptions

In addition to the standard exception classes like runtime_error or invalid_argument, you can create your own custom exceptions by extending the std::exception class.

#include <iostream>
#include <exception>

class MyCustomException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception occurred!";
    }
};

void throwCustomException() {
    throw MyCustomException();
}

int main() {
    try {
        throwCustomException(); // Will throw a custom exception
    } catch (const MyCustomException& e) {
        std::cout << "Caught custom exception: " << e.what() << std::endl;
    } catch (...) {
        std::cout << "Caught an unknown exception!" << std::endl;
    }

    return 0;
}

Output:

Caught custom exception: My custom exception occurred!

In this example:

  • We create a custom exception MyCustomException that overrides the what() function to provide a custom error message.
  • The exception is thrown in throwCustomException() and caught in the catch block.

Example 4: Exception Propagation

Exceptions in C++ are propagated from the point of throwing to the nearest catch block that can handle the exception. If no catch block is found, the exception will propagate up to the caller, and so on, until the program terminates.

#include <iostream>
#include <stdexcept>

void functionA() {
    throw std::runtime_error("Error in function A");
}

void functionB() {
    functionA(); // This will propagate the exception from functionA
}

int main() {
    try {
        functionB(); // Starts exception propagation
    } catch (const std::runtime_error& e) {
        std::cout << "Caught an exception: " << e.what() << std::endl;
    }

    return 0;
}

Output:

Caught an exception: Error in function A

In this example:

  • The exception thrown in functionA() propagates up to functionB(), and is finally caught in main().

Example 5: Catching All Exceptions with catch(...)

The catch(...) block is a catch-all handler that can be used when you don't know or care about the type of exception. It will catch any exception that isn't already caught by previous catch blocks.

#include <iostream>

void functionWithError() {
    throw 42; // Throwing an integer as an exception
}

int main() {
    try {
        functionWithError();
    } catch (int e) {
        std::cout << "Caught integer exception: " << e << std::endl;
    } catch (...) {
        std::cout << "Caught an unknown exception!" << std::endl;
    }

    return 0;
}

Output:

Caught integer exception: 42

In this example:

  • An integer exception is thrown, and it is caught specifically by the catch (int) block.
  • If the exception type was unknown, the catch(...) block would handle it.