Lesson 55 of 60 – Exception Handling in C++
92%

Exception Handling in C++

Exception handling is a mechanism used in C++ to handle unexpected situations or runtime errors in a controlled way. It allows a program to detect an exceptional condition and transfer control to code that can handle it.

Note: C++ mainly uses the try, throw, and catch keywords for exception handling.

1. What is Exception Handling?

Exception handling allows a program to respond to exceptional conditions without mixing error-handling logic with normal program logic.

For example, dividing a number by zero is an invalid operation that a program may need to handle.

try {

    // Code that may cause an exception

}
catch(...) {

    // Handle exception
}

2. Why Use Exception Handling?

Exception handling can make programs more robust by separating exceptional situations from normal program flow.

  • Handle unexpected conditions.
  • Prevent abrupt termination when an exception is properly handled.
  • Separate normal code from error-handling code.
  • Pass an error condition to a suitable handler.
  • Provide meaningful information about an exceptional situation.

3. Three Important Keywords

C++ exception handling commonly uses three keywords:

Keyword Purpose
try Contains code that may generate an exception.
throw Generates or propagates an exception.
catch Handles an exception.

4. Basic try-catch Syntax

The basic structure of exception handling is:

try {

    // Risky code

}
catch(...) {

    // Exception handling code
}

The catch block is executed when a matching exception is thrown from the associated try block.

5. Using throw

The throw statement is used to signal an exception.

int age = 15;

if(age < 18) {

    throw "Not eligible";
}

The thrown value can be handled by a suitable catch handler.

6. Simple Exception Example

#include <iostream>

int main() {

    try {

        throw 10;
    }

    catch(int value) {

        std::cout <<
            "Exception: "
            << value;
    }

    return 0;
}

The integer 10 is thrown and then handled by the catch(int value) block.

7. Catching an Integer Exception

try {

    throw 100;
}

catch(int error) {

    std::cout <<
        "Error code: "
        << error;
}

The type of the catch parameter must match the thrown exception type or be compatible with it.

8. Catching a String Exception

try {

    throw std::string(
        "Something went wrong"
    );
}

catch(const std::string& message) {

    std::cout <<
        message;
}

A string object can also be thrown and caught.

9. Catching a Standard Exception

C++ provides standard exception classes in the standard library.

#include <stdexcept>

try {

    throw std::runtime_error(
        "File could not be opened"
    );
}

catch(
    const std::runtime_error& e
) {

    std::cout <<
        e.what();
}

The what() function provides an explanatory message.

10. Division by Zero Example

#include <iostream>
#include <stdexcept>

double divide(
    double a,
    double b
) {

    if(b == 0) {

        throw std::runtime_error(
            "Cannot divide by zero"
        );
    }

    return a / b;
}

int main() {

    try {

        std::cout <<
            divide(10, 0);
    }

    catch(
        const std::runtime_error& e
    ) {

        std::cout <<
            e.what();
    }

    return 0;
}

11. Multiple catch Blocks

A single try block can be followed by multiple catch blocks.

try {

    throw 10;
}

catch(int value) {

    std::cout <<
        "Integer exception";
}

catch(double value) {

    std::cout <<
        "Double exception";
}

The first suitable handler is selected for the thrown exception.

12. Catching Different Exception Types

try {

    throw 5.5;
}

catch(int value) {

    std::cout <<
        "Integer";
}

catch(double value) {

    std::cout <<
        "Double";
}

Since a double is thrown, the matching double handler is selected.

13. Catch-All Handler

A catch-all handler uses ....

try {

    throw 100;
}

catch(...) {

    std::cout <<
        "Some exception occurred";
}

It can catch an exception of any type, although it does not directly provide the original typed value.

14. Order of catch Blocks

When using multiple handlers, more specific handlers should generally come before a catch-all handler.

try {

    throw std::runtime_error(
        "Error"
    );
}

catch(
    const std::runtime_error& e
) {

    std::cout <<
        e.what();
}

catch(...) {

    std::cout <<
        "Unknown exception";
}

A catch-all handler placed first would prevent later handlers from being reached.

15. Standard Exception Classes

The C++ standard library provides several exception classes.

  • std::exception
  • std::runtime_error
  • std::logic_error
  • std::invalid_argument
  • std::out_of_range
  • std::length_error
  • std::overflow_error
  • std::underflow_error

These classes can provide more meaningful and structured error information.

16. std::exception

std::exception is a standard base exception type.

#include <iostream>
#include <exception>

try {

    throw std::runtime_error(
        "Runtime error"
    );
}

catch(
    const std::exception& e
) {

    std::cout <<
        e.what();
}

A handler for std::exception can catch many standard library exceptions through the common base type.

17. std::invalid_argument

std::invalid_argument is useful when a function receives an argument that is not valid for the requested operation.

#include <stdexcept>

int squareRootInput(int value) {

    if(value < 0) {

        throw std::invalid_argument(
            "Value cannot be negative"
        );
    }

    return value;
}

18. std::out_of_range

std::out_of_range can be used when a value is outside the valid range of an operation.

#include <stdexcept>

int getValue(
    int index
) {

    if(index < 0 ||
       index >= 5) {

        throw std::out_of_range(
            "Index is out of range"
        );
    }

    return index;
}

19. Exception Propagation

If a function does not handle an exception, the exception can propagate back through the calling functions until a suitable handler is found.

void test() {

    throw std::runtime_error(
        "Error in test"
    );
}

void process() {

    test();
}

int main() {

    try {

        process();
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            e.what();
    }
}

20. Rethrowing an Exception

A handler can use throw; without an operand to rethrow the currently handled exception.

try {

    try {

        throw std::runtime_error(
            "Original error"
        );
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            "Logging error\n";

        throw;
    }
}

catch(
    const std::exception& e
) {

    std::cout <<
        e.what();
}

Rethrowing is useful when one layer needs to log or partially handle an exception while allowing another layer to handle it further.

21. Custom Exception Class

You can create your own exception class when application-specific error information is useful.

#include <exception>

class AgeException :
    public std::exception {

public:

    const char* what()
        const noexcept override {

        return
            "Age is not valid";
    }
};

The custom exception can then be thrown and caught like other exceptions.

22. Throwing a Custom Exception

class AgeException :
    public std::exception {

public:

    const char* what()
        const noexcept override {

        return
            "Age must be 18 or above";
    }
};

void checkAge(int age) {

    if(age < 18) {

        throw AgeException();
    }
}

int main() {

    try {

        checkAge(15);
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            e.what();
    }

    return 0;
}

23. Exception Handling with Functions

double divide(
    double a,
    double b
) {

    if(b == 0) {

        throw std::invalid_argument(
            "Division by zero"
        );
    }

    return a / b;
}

int main() {

    try {

        double result =
            divide(20, 0);

        std::cout <<
            result;
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            e.what();
    }

    return 0;
}

A function can throw an exception and let the caller decide how to handle it.

24. Exception Handling and Constructors

Constructors can also throw exceptions when an object cannot be created in a valid state.

class Student {

private:

    int age;

public:

    Student(int a) {

        if(a < 0) {

            throw std::invalid_argument(
                "Age cannot be negative"
            );
        }

        age = a;
    }
};

The caller can catch the exception when constructing the object.

25. Exception Safety and Resource Management

Exception-safe code should manage resources carefully. Modern C++ commonly uses RAII and standard library resource-managing types to ensure cleanup happens automatically.

#include <memory>

void process() {

    auto value =
        std::make_unique<int>(100);

    // If an exception occurs,
    // the resource is automatically released.
}

Using RAII reduces the risk of resource leaks when exceptions occur.

26. Common Mistakes in Exception Handling

  • Using exceptions for ordinary program control flow.
  • Catching exceptions by value when catching by reference is more appropriate.
  • Using a catch-all handler without considering specific exceptions.
  • Ignoring useful exception information.
  • Throwing unclear or meaningless error messages.
  • Forgetting to release resources in code that is not exception-safe.
  • Catching an exception and silently doing nothing.
  • Writing unnecessarily large try blocks.

27. Advantages of Exception Handling

  • Separation: Error-handling code can be separated from normal logic.
  • Propagation: An exception can move to a suitable handler.
  • Clarity: Exceptional situations can be represented explicitly.
  • Robustness: Proper handling can prevent unexpected program termination.
  • Standardization: Standard exception classes provide common error types.
  • Resource Safety: RAII can help manage resources safely when exceptions occur.

28. Best Practices for Exception Handling

  • Throw exceptions for exceptional situations rather than normal control flow.
  • Catch exceptions by const reference when appropriate.
  • Catch specific exception types before general handlers.
  • Use standard exception classes when they accurately describe the problem.
  • Provide meaningful error messages.
  • Use RAII and standard resource-managing classes.
  • Do not silently ignore exceptions unless there is a deliberate reason.
  • Keep exception handling close to the layer that can meaningfully respond to the error.

29. Practical Student Validation Example

#include <iostream>
#include <stdexcept>

class Student {

private:

    int marks;

public:

    Student(int m) {

        if(m < 0 || m > 100) {

            throw std::out_of_range(
                "Marks must be between 0 and 100"
            );
        }

        marks = m;
    }

    void display() {

        std::cout <<
            "Marks: "
            << marks;
    }
};

int main() {

    try {

        Student student(120);

        student.display();
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            "Error: "
            << e.what();
    }

    return 0;
}

The constructor validates the data and throws an exception when the value is outside the allowed range.

30. Exception Handling – Final Summary

Concept Meaning
try Contains code that may throw an exception.
throw Signals or propagates an exception.
catch Handles a matching exception.
std::exception Common standard exception base type.
Custom Exception User-defined exception type for application-specific errors.
Rethrow Uses throw; to propagate the currently handled exception.
RAII Resource management technique that helps provide safe cleanup, including during exception handling.
#include <iostream>
#include <stdexcept>

double divide(
    double a,
    double b
) {

    if(b == 0) {

        throw std::invalid_argument(
            "Cannot divide by zero"
        );
    }

    return a / b;
}

int main() {

    try {

        std::cout <<
            divide(20, 0);
    }

    catch(
        const std::exception& e
    ) {

        std::cout <<
            "Error: "
            << e.what();
    }

    return 0;
}

Exception handling provides a structured way to detect and handle exceptional situations while keeping normal program logic separate from error-handling logic.

📌 Key Points

  • C++ uses try, throw, and catch for exception handling.
  • try contains code that may generate an exception.
  • throw signals an exceptional condition.
  • catch handles a matching exception.
  • Multiple catch blocks can handle different exception types.
  • catch(...) can handle an exception of any type.
  • C++ provides standard exception classes such as std::runtime_error and std::invalid_argument.
  • Exceptions can propagate through function calls.
  • A custom exception class can be created when needed.
  • RAII and resource-managing types help make programs exception-safe.

🧠 Quick Quiz

Question: Which three keywords are mainly used for exception handling in C++?