exception_safety


Concept: Exception Safety

Exception safety is a crucial concept in modern C++ programming that ensures robust and reliable code in the face of unexpected errors. It refers to the ability of a program to handle exceptions gracefully, maintaining program invariants and preventing resource leaks. In this exploration, we'll look at different levels of exception safety and how to implement them in C++.

Example 1: Basic Exception Safety (No-throw guarantee)

#include <iostream>
#include <stdexcept>
#include <vector>

class ResourceManager {
private:
    std::vector<int> data;

public:
    void addData(int value) noexcept {
        try {
            data.push_back(value);
        } catch (const std::bad_alloc&) {
            // Handle allocation failure
            std::cerr << "Memory allocation failed. Data not added." << std::endl;
        }
    }

    void displayData() const noexcept {
        for (const auto& item : data) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    ResourceManager rm;
    rm.addData(10);
    rm.addData(20);
    rm.addData(30);
    rm.displayData();
    return 0;
}

Explanation

This example demonstrates basic exception safety, also known as the no-throw guarantee:

Example 2: Strong Exception Safety (Commit-or-rollback semantics)

#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>

class Database {
private:
    std::vector<int> data;

public:
    void addBatch(const std::vector<int>& newData) {
        auto oldSize = data.size();
        try {
            data.insert(data.end(), newData.begin(), newData.end());
        } catch (...) {
            // Rollback on any exception
            data.resize(oldSize);
            throw; // Re-throw the caught exception
        }
    }

    void display() const {
        for (const auto& item : data) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    Database db;
    std::vector<int> batch1 = {1, 2, 3};
    std::vector<int> batch2 = {4, 5, 6};

    try {
        db.addBatch(batch1);
        db.addBatch(batch2);
    } catch (const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }

    db.display();
    return 0;
}

Explanation

This example illustrates strong exception safety:

Example 3: Basic Exception Safety with RAII

#include <iostream>
#include <memory>
#include <stdexcept>

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource released" << std::endl; }
    void use() { std::cout << "Resource used" << std::endl; }
};

class ResourceUser {
private:
    std::unique_ptr<Resource> resource;

public:
    ResourceUser() : resource(std::make_unique<Resource>()) {}

    void performOperation() {
        resource->use();
        // Simulating an operation that might throw
        throw std::runtime_error("Operation failed");
    }
};

int main() {
    try {
        ResourceUser user;
        user.performOperation();
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
    return 0;
}

Explanation

This example showcases basic exception safety using RAII (Resource Acquisition Is Initialization):

Example 4: Exception Safety in Constructors

#include <iostream>
#include <stdexcept>
#include <memory>

class SubComponent {
public:
    SubComponent(int id) {
        if (id < 0) throw std::invalid_argument("Invalid ID");
        std::cout << "SubComponent " << id << " created" << std::endl;
    }
    ~SubComponent() { std::cout << "SubComponent destroyed" << std::endl; }
};

class MainComponent {
private:
    std::unique_ptr<SubComponent> sub1;
    std::unique_ptr<SubComponent> sub2;

public:
    MainComponent(int id1, int id2) 
        : sub1(std::make_unique<SubComponent>(id1)),
          sub2(std::make_unique<SubComponent>(id2)) {
        std::cout << "MainComponent created" << std::endl;
    }

    ~MainComponent() { std::cout << "MainComponent destroyed" << std::endl; }
};

int main() {
    try {
        MainComponent comp(1, -2);
    } catch (const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }
    return 0;
}

Explanation

This example demonstrates exception safety in constructors:

Summary

Exception safety is a critical aspect of writing robust C++ code. The examples provided illustrate different levels and techniques of exception safety:

  1. Basic Exception Safety (No-throw guarantee): Ensures that operations complete without throwing exceptions, handling potential errors internally.

  2. Strong Exception Safety: Implements commit-or-rollback semantics, ensuring that operations either complete fully or leave the object in its original state.

  3. RAII (Resource Acquisition Is Initialization): Uses smart pointers and RAII principles to manage resources, ensuring proper cleanup even in the face of exceptions.

  4. Exception Safety in Constructors: Demonstrates how to handle exceptions during object construction, preventing resource leaks and maintaining invariants.

By applying these techniques, C++ programmers can create more reliable and robust software that gracefully handles unexpected situations. Remember, the goal of exception safety is not just to catch exceptions, but to ensure that your program remains in a consistent state and continues to function correctly even when errors occur.

Related

Previous Page | Course Schedule | Course Content