Back to C++ Home

Exception Handling

Exception handling is a robust error-handling mechanism that allows programs to deal with unexpected situations gracefully. Instead of programs crashing or returning error codes that might be ignored, exceptions provide a structured way to handle errors, propagate them up the call stack, and ensure proper cleanup. This makes programs more reliable, maintainable, and user-friendly.

Introduction to Exception Handling

Exception handling is a mechanism to handle runtime errors gracefully. It allows programs to continue execution or terminate gracefully when errors occur, rather than crashing unexpectedly. Exceptions separate error-handling code from normal program flow, making code more readable and maintainable.

When an exceptional condition occurs, a function can throw an exception. The exception propagates up the call stack until it's caught by an appropriate catch block. If no catch block handles it, the program terminates. This ensures errors are not silently ignored and provides a way to handle them at the appropriate level.

Exception handling works together with RAII (Resource Acquisition Is Initialization) to ensure resources are properly cleaned up even when exceptions occur. Destructors are automatically called during stack unwinding, ensuring memory and other resources are freed.

1. Basic Exception Handling

try-catch Block

#include <iostream>
using namespace std;

int main() {
    try {
        // Code that may throw exception
        int num = 10;
        int den = 0;
        
        if (den == 0) {
            throw "Division by zero!";
        }
        
        int result = num / den;
        cout << result << endl;
    }
    catch (const char* msg) {
        // Handle exception
        cout << "Error: " << msg << endl;
    }
    
    return 0;
}

2. Multiple catch Blocks

#include <iostream>
using namespace std;

int main() {
    try {
        int choice;
        cin >> choice;
        
        if (choice == 1) {
            throw 10;  // Integer exception
        } else if (choice == 2) {
            throw "Error message";  // String exception
        } else {
            throw 3.14;  // Double exception
        }
    }
    catch (int e) {
        cout << "Integer exception: " << e << endl;
    }
    catch (const char* e) {
        cout << "String exception: " << e << endl;
    }
    catch (...) {
        cout << "Unknown exception" << endl;
    }
    
    return 0;
}

3. Standard Exception Classes

C++ provides standard exception classes in <exception> header:

Exception ClassDescription
exceptionBase class for all exceptions
runtime_errorRuntime errors
logic_errorLogic errors
invalid_argumentInvalid argument
out_of_rangeOut of range
bad_allocMemory allocation failure

4. Using Standard Exceptions

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

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

int main() {
    try {
        double result = divide(10, 0);
        cout << result << endl;
    }
    catch (const runtime_error& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    return 0;
}

5. Custom Exception Classes

#include <iostream>
#include <exception>
#include <string>
using namespace std;

class MyException : public exception {
private:
    string message;

public:
    MyException(const string& msg) : message(msg) {}
    
    const char* what() const throw() {
        return message.c_str();
    }
};

int main() {
    try {
        throw MyException("Custom exception occurred!");
    }
    catch (const MyException& e) {
        cout << "Caught: " << e.what() << endl;
    }
    
    return 0;
}

6. Nested try-catch

#include <iostream>
using namespace std;

int main() {
    try {
        try {
            throw "Inner exception";
        }
        catch (const char* e) {
            cout << "Inner catch: " << e << endl;
            throw;  // Re-throw exception
        }
    }
    catch (const char* e) {
        cout << "Outer catch: " << e << endl;
    }
    
    return 0;
}

7. Exception Specifications

Functions can specify which exceptions they might throw (deprecated in C++11, but still used):

#include <iostream>
using namespace std;

void func() throw(int, const char*) {
    // Can only throw int or const char*
    throw 10;
}

void noThrow() throw() {
    // Cannot throw any exception
    // If exception occurs, unexpected() is called
}

int main() {
    try {
        func();
    }
    catch (int e) {
        cout << "Caught int: " << e << endl;
    }
    
    return 0;
}

8. Complete Example

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

class Calculator {
public:
    double divide(double a, double b) {
        if (b == 0) {
            throw runtime_error("Cannot divide by zero!");
        }
        return a / b;
    }
    
    int factorial(int n) {
        if (n < 0) {
            throw invalid_argument("Factorial of negative number!");
        }
        if (n > 20) {
            throw out_of_range("Number too large!");
        }
        
        int result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }
};

int main() {
    Calculator calc;
    
    try {
        cout << calc.divide(10, 2) << endl;
        cout << calc.divide(10, 0) << endl;
    }
    catch (const runtime_error& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    try {
        cout << calc.factorial(5) << endl;
        cout << calc.factorial(-1) << endl;
    }
    catch (const invalid_argument& e) {
        cout << "Error: " << e.what() << endl;
    }
    catch (const out_of_range& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    return 0;
}

9. Best Practices

  • Use exceptions for exceptional conditions, not for normal control flow
  • Catch exceptions by reference (const reference preferred)
  • Use specific exception types rather than generic ones
  • Always clean up resources (use RAII - Resource Acquisition Is Initialization)
  • Don't throw exceptions from destructors
  • Document which exceptions functions can throw
  • Use standard exception classes when possible

Summary

TopicKey PointsDifficulty
try-catch Blockstry contains code that may throw, catch handles exceptions, multiple catch blocks for different typesBeginner
throw StatementThrows exception object, can throw any type, propagates up call stackBeginner
Standard Exceptionsexception, runtime_error, logic_error, invalid_argument, out_of_range, bad_allocIntermediate
Custom ExceptionsDerive from exception class, override what() method, provide meaningful error messagesIntermediate
Exception PropagationExceptions propagate up call stack until caught, stack unwinding calls destructorsIntermediate
RAII and ExceptionsDestructors called during stack unwinding, ensures resource cleanup even with exceptionsIntermediate

Frequently Asked Questions

Q1: What happens if an exception is thrown but not caught?

If an exception propagates to main() without being caught, std::terminate() is called, which by default calls abort(), terminating the program. This is usually undesirable. Always catch exceptions at appropriate levels. You can set a terminate handler, but it's better to catch exceptions properly.

Q2: Should I catch exceptions by value or by reference?

Always catch by const reference: catch (const std::exception& e). Catching by value causes slicing (loses derived class information) and unnecessary copying. Catching by reference avoids copying, preserves polymorphism, and allows accessing derived class members. Const reference is preferred to prevent modification.

Q3: What is stack unwinding and how does it work?

Stack unwinding occurs when an exception is thrown. The runtime destroys all automatic objects (local variables) in reverse order of construction as it searches for a catch block. Destructors are called automatically, ensuring proper cleanup. This is why RAII works so well with exceptions - resources are automatically freed during unwinding.

Q4: Can I throw exceptions from constructors?

Yes, throwing from constructors is acceptable and often the best way to handle construction failures. If constructor throws, object is not created, destructor is not called (object was never fully constructed). However, destructors of already-constructed member objects and base classes are called. This is safe and recommended for handling construction errors.

Q5: Why shouldn't I throw exceptions from destructors?

Throwing from destructors is dangerous because destructors are called during stack unwinding. If an exception is thrown during unwinding,std::terminate() is called, terminating the program. This prevents proper cleanup. Destructors should handle errors internally (log, set flags) but not throw. If you must throw, mark destructor noexcept(false)(not recommended).

Q6: What is the catch-all handler (catch (...))?

catch (...) catches any exception type. Use it as last catch block to handle unexpected exceptions. However, you can't access the exception object, so use it for logging or cleanup, then re-throw or terminate. It's useful for ensuring cleanup happens even for unknown exceptions, but prefer specific catch blocks when possible.

Q7: What is exception safety and what are the guarantee levels?

Exception safety guarantees: basic (no leaks, valid state), strong (rollback on exception, all-or-nothing), nothrow (never throws). Strong guarantee means operation either completes or has no effect (transactional). Basic guarantee means program remains in valid state but may be modified. Strive for strong guarantee when possible, at minimum basic guarantee.

Q8: Are exceptions expensive in terms of performance?

Exception handling has minimal overhead when no exceptions occur (zero-cost in normal path). Throwing exceptions has overhead (stack unwinding, finding catch block), but this is acceptable since exceptions should be exceptional. Don't use exceptions for normal control flow. For error handling, exceptions are often more efficient than checking return codes everywhere.

Conclusion

Exception handling is essential for writing robust, reliable C++ programs. It provides a structured way to handle errors, separate error handling from normal code flow, and ensure proper resource cleanup through RAII. Understanding exceptions enables you to write code that gracefully handles unexpected situations.

Standard exception classes provide a hierarchy of error types, while custom exceptions allow you to create domain-specific error handling. The combination of exceptions and RAII ensures that resources are properly managed even when errors occur, preventing memory leaks and resource exhaustion.

Mastery of exception handling is crucial for professional C++ programming. It enables you to write code that's both safe and maintainable, with clear error handling that doesn't clutter the main program logic. Used correctly, exceptions make programs more reliable and easier to debug and maintain.

Related Links

🔹 Author: Dr. J. Siva Ramakrishna

🔹 Institution: Narayana Engineering College, Gudur

🔹 Last Updated: 9 January 2026