Exception Safety Guarantees and Copy & Swap
Error Handling and Exception Safety

9.2 Exception Safety Guarantees and Copy & Swap

Exception safety is not about catching errors; it is about keeping your data consistent when an error occurs. If a function throws an exception halfway through modifying an object, the object can be left in a corrupted state. C++ developers design code to satisfy specific safety guarantees.

Let's look at safety levels and the copy and swap idiom.

The Three Exception Safety Levels

When you write code that can throw exceptions, you should aim to satisfy one of three safety guarantees:

* Basic Guarantee: If an exception is thrown, no memory or resource is leaked. However, the modified objects can be left in a valid but unspecified state. You can safely destroy them, but their values might be altered.

* Strong Guarantee: If an exception is thrown, the state of the program is completely unchanged (as if the function was never called). This provides transaction like success or rollback behavior.

* Nothrow Guarantee: The function promises never to throw an exception under any circumstances. Functions like destructors and move operators must satisfy this guarantee.

The Copy and Swap Idiom

The copy and swap idiom is a design pattern that provides the Strong Exception Safety Guarantee. Instead of modifying the current object directly (which could throw an exception and leave it half updated), you follow these steps:

1. Create a temporary copy of the object.

2. Perform all risky, exception prone modifications on the temporary copy.

3. Once all operations succeed, swap the internal data of the current object with the copy using a no throw swap operation.

If an exception is thrown during step 2, the temporary copy is automatically destroyed, and the original object remains completely untouched.

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

class DataManager {
    std::vector<int> data;
public:
    // Swap function marked noexcept (nothrow guarantee)
    void swap(DataManager& other) noexcept {
        std::swap(data, other.data);
    }

    // Assignment operator using copy and swap
    DataManager& operator=(DataManager temp) {
        // temp is passed by value (automatically copied!)
        // If copy throws, parent function catches it and state is unchanged.
        this->swap(temp); // Swap internal pointers (no throw!)
        return *this;
    } // temp exits scope here, carrying the old data to its destruction!
};
Achieving strong exception safety with copy and swap.

* The Draft Paper Metaphor: The copy and swap idiom is like signing house paperwork. You write and edit all the clauses on a draft copy. If you spill coffee on it or the deal falls through (an exception is thrown), your money and real house remain untouched. Only when everything is perfect do you sign and swap keys.

The Destructor Nothrow Rule (Double Exception Crashes)

In C++, destructors are implicitly marked noexcept by default. You must never allow an exception to escape from a destructor.

If an exception is thrown inside a destructor while another exception is already active (which happens during stack frame unwinding), the C++ runtime environment cannot handle two concurrent exceptions. It immediately calls std::terminate to abort the application.

If your destructor performs cleanups that can throw exceptions (such as closing databases or writing logs to disk), you must catch and swallow those exceptions inside the destructor scope:

#include <iostream>
#include <stdexcept>

struct FileHandle {
    ~FileHandle() noexcept {
        try {
            // Risky operation that could throw
            throw std::runtime_error("Cleanup failed!");
        } catch (...) {
            // Swallow exception to prevent double exception aborts!
            std::cerr << "Error swallowed during destruction\n";
        }
    }
};
Swallowing exceptions inside destructors.
Finished reading this lesson?