13

Module 13: Exception Handling

Chapter 13 • Advanced

50 min

Exception Handling

Exception handling allows programs to deal with errors gracefully instead of crashing. It's essential for writing robust, production-quality C++ code.

What are Exceptions?

Exceptions are runtime errors or unusual conditions that disrupt normal program flow. Instead of crashing, exceptions can be caught and handled.

Benefits:

  • Error Recovery: Handle errors without crashing
  • Clean Code: Separate error handling from business logic
  • Resource Safety: Ensure cleanup even when errors occur
  • Information Propagation: Pass error information up call stack

Exception Handling Syntax

Basic try-catch Block

cpp.js
try {
    // Code that might throw exception
    riskyFunction();
} catch (exception_type e) {
    // Handle exception
    cout << "Error: " << e.what() << endl;
}

Example

cpp.js
#include <iostream>
#include <stdexcept>
using namespace std;

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

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

Standard Exception Classes

C++ provides standard exception hierarchy:

cpp.js
exception
├── logic_error
│   ├── invalid_argument
│   ├── domain_error
│   ├── length_error
│   └── out_of_range
├── runtime_error
│   ├── range_error
│   ├── overflow_error
│   ├── underflow_error
│   └── system_error
└── bad_alloc (from new)

Using Standard Exceptions

cpp.js
#include <stdexcept>

// Invalid argument
if (value < 0) {
    throw invalid_argument("Value must be non-negative");
}

// Out of range
if (index >= size) {
    throw out_of_range("Index out of bounds");
}

// Runtime error
if (fileNotFound) {
    throw runtime_error("File not found");
}

Multiple catch Blocks

Handle different exception types:

cpp.js
try {
    // Code that might throw
} catch (invalid_argument& e) {
    // Handle invalid argument
} catch (out_of_range& e) {
    // Handle out of range
} catch (exception& e) {
    // Handle any other exception
}

Order Matters: More specific exceptions first, general last.

Exception Propagation

Exceptions propagate up the call stack until caught:

cpp.js
void function3() {
    throw runtime_error("Error in function3");
}

void function2() {
    function3();  // Exception propagates
}

void function1() {
    try {
        function2();
    } catch (runtime_error& e) {
        cout << "Caught: " << e.what() << endl;
    }
}

Creating Custom Exceptions

Derive from standard exception classes:

cpp.js
class MyException : public runtime_error {
public:
    MyException(const string& msg) : runtime_error(msg) {}
};

// Usage
throw MyException("Custom error message");

Exception Specifications (Deprecated)

Old Style (C++98):

cpp.js
void function() throw(runtime_error);  // Deprecated!

Modern Style (C++11):

cpp.js
void function() noexcept;  // No exceptions
void function() noexcept(false);  // May throw

RAII and Exception Safety

RAII (Resource Acquisition Is Initialization) ensures resources are cleaned up even when exceptions occur.

Without RAII (Problem)

cpp.js
void badFunction() {
    int* ptr = new int(10);
    riskyOperation();  // If this throws, memory leak!
    delete ptr;
}

With RAII (Solution)

cpp.js
void goodFunction() {
    unique_ptr<int> ptr = make_unique<int>(10);
    riskyOperation();  // If this throws, ptr automatically deleted!
}

Exception Safety Guarantees

  1. Basic Guarantee: No resource leaks, valid state
  2. Strong Guarantee: Operation succeeds or state unchanged
  3. No-throw Guarantee: Never throws exceptions

noexcept Specifier

Mark functions that don't throw:

cpp.js
void safeFunction() noexcept {
    // This function promises not to throw
}

// Conditional noexcept
template <typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
}

Best Practices

  1. Use exceptions for exceptional conditions
  2. Catch by reference (not by value)
  3. Use specific exception types when possible
  4. Don't throw from destructors (undefined behavior)
  5. Use RAII for resource management
  6. Document exceptions in function comments
  7. Catch specific exceptions first, general last
  8. Re-throw with `throw;` to preserve stack trace

Common Patterns

Pattern 1: Resource Guard

cpp.js
class FileGuard {
    FILE* file;
public:
    FileGuard(const char* name) : file(fopen(name, "r")) {
        if (!file) throw runtime_error("Cannot open file");
    }
    ~FileGuard() { if (file) fclose(file); }
    FILE* get() { return file; }
};

Pattern 2: Exception Wrapper

cpp.js
template <typename Func>
auto safeCall(Func f) {
    try {
        return f();
    } catch (exception& e) {
        cout << "Error: " << e.what() << endl;
        throw;  // Re-throw
    }
}

Common Mistakes

  • ❌ Catching by value instead of reference
  • ❌ Catching exception before specific types
  • ❌ Throwing from destructors
  • ❌ Swallowing exceptions silently
  • ❌ Using exceptions for control flow
  • ❌ Not cleaning up resources in catch blocks
  • ❌ Forgetting to re-throw when needed

Exception vs Error Codes

AspectExceptionsError Codes
**Performance**Slower (when thrown)Fast
**Visibility**Must be handledEasy to ignore
**Information**Rich (what(), type)Limited
**Control Flow**Automatic propagationManual checking
**Use Case**Exceptional conditionsExpected errors

Next Module

In Module 14, we'll learn about File I/O - reading and writing files in C++!

Hands-on Examples

Basic Exception Handling

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

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

int getElement(int arr[], int size, int index) {
    if (index < 0 || index >= size) {
        throw out_of_range("Index out of bounds!");
    }
    return arr[index];
}

int main() {
    // Example 1: Division by zero
    cout << "=== Example 1: Division ===" << endl;
    try {
        int result = divide(10, 0);
        cout << "Result: " << result << endl;
    } catch (runtime_error& e) {
        cout << "Caught exception: " << e.what() << endl;
    }
    
    // Example 2: Array bounds
    cout << "\n=== Example 2: Array Access ===" << endl;
    int arr[] = {10, 20, 30, 40, 50};
    try {
        int value = getElement(arr, 5, 10);  // Invalid index
        cout << "Value: " << value << endl;
    } catch (out_of_range& e) {
        cout << "Caught exception: " << e.what() << endl;
    }
    
    // Example 3: Successful operation
    cout << "\n=== Example 3: Success ===" << endl;
    try {
        int result = divide(20, 4);
        cout << "Result: " << result << endl;
        
        int value = getElement(arr, 5, 2);
        cout << "Array value: " << value << endl;
    } catch (exception& e) {
        cout << "Caught: " << e.what() << endl;
    }
    
    return 0;
}

Basic exception handling uses try-catch blocks. Code that might throw is in try block. catch blocks handle specific exception types. Exceptions propagate up call stack until caught. Use standard exception types from <stdexcept>. Always catch by reference, not by value.