C++ / a working model

88 / 163   ·   C++11   ·   13 min

Exception safety: prepare first, then commit the state

Keep this sentence

RAII can reclaim resources when leaving the scope, but it does not automatically undo business state that has already been written. Using configuration replacement to demonstrate how to put work that may fail into a local object, confirm it is valid, then swap, so that the failure path keeps the old configuration unchanged.

In this lesson
  1. Resource safety and state safety are not the same thing
  2. The local candidate object is the preparation area
  3. Turn historical mechanisms into modern implementations
  4. Example
  5. Exercise
READING EVIDENCE / Full text read

Thinking in C++

The two volumes are recorded as the same work: consecutive reading of Volume 1 chapters 1–16, appendices A–C, preface, and all end-of-chapter exercises/footnotes; Volume 2 chapters 1–11, appendices A/B, introduction, index, and footnotes 1–162. In addition, the images attached to the HTML of both volumes were read directly, one by one, to complete the class diagrams, layout diagrams, and recurrence formulas missing from the plain text. Exercises were not executed; separately sold solutions, CD recordings, and external linked materials are not included in the read scope. Complete reading does not mean that historical examples comply with C++20.

Edition, actual reading range, and original sources →

Resource safety and state safety are not the same thing

A main thread between the two volumes is from managing responsibility with construction and destruction, toward maintaining understandable object state even when exceptions propagate. Members that own resources can clean up automatically, but if a function first changes one field and then fails while parsing another field, the destructor will not restore the previous write for you.

Therefore, first write the interface promise: successful configuration import replaces as a whole, failure retains the original configuration. This example limits the input to a set of non-empty names, without discussing file systems or cross-process transactions. This boundary allows us to clearly analyze every location that might throw an exception, rather than treating exception safety as an inherent property of all C++ objects.

The local candidate object is the preparation area

First put the new names one by one into a local vector. Empty names trigger an exception, and allocation failure may also throw; these situations all occur before the old configuration has been touched. The local container is responsible for releasing the prepared elements, and the caller only needs to decide how to handle the failure, without needing to know on which name the failure occurred.

After all checks are complete, then swap. Here a vector with the default allocator is used; the swap does not copy strings and does not need to reallocate elements; the commit step will not introduce these failure points again. The strong guarantee comes from this specific arrangement and cannot be generalized to “any two objects swapping never throws an exception”. Custom allocators and custom swap need their contracts checked separately.

Turn historical mechanisms into modern implementations

The original book uses raw pointers, auto_ptr, and handwritten resource classes to reveal how responsibility is lost. Today it is sufficient to retain its ownership ideas: containers take on sequential resources, without copying old-style reference counting or dynamic exception specifications just for demonstration. auto_ptr was removed in C++17, and throw specifications with type lists cannot be used as C++20 interfaces either.

The example verifies the behavior seen by the consumer: legal input changes the configuration, when the second item is illegal the complete old value remains unchanged, an empty list can clear it. In tests compare the entire sequence rather than just checking the length, because preserving the length does not mean preserving the content. Transactional objects may temporarily occupy extra memory; this is the explicit cost here for a clear failure guarantee.

Pitfalls

  • RAII reclaiming resources is not the same as rolling back business data; modifying members first then checking may only get the basic guarantee.
  • The non-throwing nature of swap requires specific type and allocator conditions; do not use this example to infer that all swaps are safe.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
class Config {
    std::vector<std::string> names_;
public:
    const std::vector<std::string>& names() const noexcept { return names_; }
    void replace(const std::vector<std::string>& input) {
        std::vector<std::string> candidate;
        candidate.reserve(input.size());
        for (const auto& name : input) {
            if (name.empty()) throw std::invalid_argument("empty name");
            candidate.push_back(name);
        }
        names_.swap(candidate);
    }
};
int main() {
    Config config;
    config.replace({"alpha", "beta"});
    const auto before = config.names();
    bool rejected = false;
    try { config.replace({"changed", ""}); }
    catch (const std::invalid_argument&) { rejected = true; }
    assert(rejected && config.names() == before);
    config.replace({});
    assert(config.names().empty());
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-thinking-cpp.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

Add a rule that names cannot be duplicated, still keeping failure from changing the old configuration.

Show a reference answer

In the candidate preparation stage, use std::find(candidate.begin(), candidate.end(), name) to check already added names; throw invalid_argument on hit, otherwise push_back. Add <algorithm>. Input {alpha, alpha} should fail, then compare config.names() with before completely equal. Swap remains the only commit point, the search does not modify the old configuration; this simple scheme is quadratic complexity, suitable for the small set here.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog