Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Advanced Features

Exception Handling

Exception Handling: Error Propagation Mechanism

Exceptions provide a structured way to handle errors, separating error handling from normal flow. When an exception is thrown, stack unwinding occurs: all local objects are destroyed until a matching catch handler is found.

Basic Syntax

#include <stdexcept>

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

try {
    double result = divide(10, 0);
} catch (const std::runtime_error& e) {
    std::cerr << "Error: " << e.what() << '\\n';
} catch (const std::exception& e) {
    std::cerr << "Exception: " << e.what() << '\\n';
} catch (...) {
    std::cerr << "Unknown exception\\n";
}

Standard Exception Hierarchy

All standard exceptions derive from std::exception:

std::exception
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   └── std::out_of_range
├── std::runtime_error
│   ├── std::range_error
│   ├── std::overflow_error
│   └── std::underflow_error
└── std::bad_alloc
throw std::invalid_argument("Invalid input");
throw std::out_of_range("Index out of bounds");
throw std::bad_alloc();  // Memory allocation failed

Custom Exceptions

class FileError : public std::runtime_error {
    std::string filename_;
public:
    FileError(const std::string& file, const std::string& msg)
        : std::runtime_error(msg), filename_(file) {}
    
    const std::string& filename() const { return filename_; }
};

void openFile(const std::string& name) {
    // ...
    throw FileError(name, "Failed to open file");
}

try {
    openFile("data.txt");
} catch (const FileError& e) {
    std::cerr << "File error in " << e.filename() 
              << ": " << e.what() << '\\n';
}

Exception Safety Guarantees

Code can provide different levels of exception safety:

1. No-Throw Guarantee

Operation never throws (or only throws if program terminates):

void swap(T& a, T& b) noexcept {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

2. Strong Exception Safety (Transactional)

If an exception is thrown, program state is unchanged:

void push_back(const T& value) {
    if (size_ == capacity_) {
        // Allocate new buffer
        T* new_data = new T[capacity_ * 2];
        
        // Copy old data (might throw)
        std::uninitialized_copy(data_, data_ + size_, new_data);
        
        // Only now commit changes (no-throw operations)
        delete[] data_;
        data_ = new_data;
        capacity_ *= 2;
    }
    data_[size_++] = value;
}

3. Basic Exception Safety

Invariants are preserved, no resources leak, but state may change:

void process() {
    auto ptr = new Resource;  // Potential leak if next line throws
    risky_operation();
    delete ptr;
}

// Better with RAII:
void process() {
    auto ptr = std::make_unique<Resource>();  // Automatic cleanup
    risky_operation();
}

4. No Exception Safety

Undefined behavior or resource leaks if exception thrown.

RAII and Exception Safety

RAII (Resource Acquisition Is Initialization) ensures cleanup even during exceptions:

class FileHandle {
    FILE* file_;
public:
    FileHandle(const char* name) : file_(fopen(name, "r")) {
        if (!file_) throw std::runtime_error("Cannot open file");
    }
    
    ~FileHandle() {
        if (file_) fclose(file_);  // Always called during stack unwinding
    }
    
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
};

void process() {
    FileHandle f("data.txt");
    // Use file...
    throw std::runtime_error("Error");  // File automatically closed
}

The noexcept Specifier

Declares that a function won’t throw exceptions:

void guaranteed() noexcept {
    // If an exception escapes, std::terminate is called
}

template<typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    // noexcept if T's move constructor is noexcept
}

Importance: noexcept enables optimizations. Standard containers use move operations only if noexcept.

class Widget {
public:
    Widget(Widget&&) noexcept;  // vector will use move
    // Widget(Widget&&);  // vector will use copy instead (for safety)
};

Conditional noexcept

template<typename T>
class Stack {
public:
    void push(const T& value) noexcept(std::is_nothrow_copy_constructible_v<T>) {
        // noexcept if T is nothrow copyable
    }
};

Rethrowing Exceptions

try {
    risky_operation();
} catch (const std::exception& e) {
    log_error(e.what());
    throw;  // Rethrow same exception (preserves type)
}

// DON'T do this:
catch (const std::exception& e) {
    throw e;  // Slices exception to std::exception!
}

Function try Blocks

Handle exceptions in constructor initialization:

class Widget {
    Resource r_;
public:
    Widget(const std::string& name)
    try : r_(name) {  // Function try block
        // Constructor body
    }
    catch (const std::exception& e) {
        // Handle exception from r_ construction
        // Note: Exception is always rethrown after this catch block
    }
};

Exception Specifications (Deprecated)

C++98 dynamic exception specifications are deprecated:

void func() throw(std::runtime_error);  // Deprecated, don't use

void func() noexcept;  // Modern C++
Conceptual Check

What happens when an exception is thrown but not caught?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <stdexcept>
3#include <memory>
4 
5class Resource {
6public:
7 Resource() { std::cout << "Resource acquired\n"; }
8 ~Resource() { std::cout << "Resource released\n"; }
9};
10 
11void dangerous() {
12 auto r = std::make_unique<Resource>();
13 throw std::runtime_error("Error!");
14}
15 
16int main() {
17 try {
18 dangerous();
19 } catch (const std::exception& e) {
20 std::cout << "Caught: " << e.what() << '\n';
21 }
22 std::cout << "Program continues\n";
23 return 0;
24}
System Console

Waiting for signal...