C++ PROGRAMMING:Module 13: Exception Handling

Mastering module 13: exception handling concepts and implementation.

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

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

Example

#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:

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

#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:

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:

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:

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):

void function() throw(runtime_error);  // Deprecated!

Modern Style (C++11):

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)

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

With RAII (Solution)

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:

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

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

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.