C++ / a working model

84 / 163   ·   C++11   ·   14 min

Construction failure: the complete object is not destroyed, but members are still cleaned up

Keep this sentence

When a constructor throws, the incomplete complete object does not have its destructor called, but already-constructed members are destroyed in reverse order. Use an allocation-free event log to trace construction, the throw, and cleanup, distinguishing object lifetime, member lifetime, and allocated storage.

In this lesson
  1. First see which object the rule applies to
  2. Put resource responsibility into members that can complete construction
  3. A historical draft cannot impersonate today's full standard
  4. Example
  5. Exercise
READING EVIDENCE / Full text read

The C++ Standard

Fully read the HTML body of the public N3337 working draft, all 30 chapters and annexes A–E, including all visible code, grammar, requirement tables, notes, and footnotes. Chapters 1 and 15 were read by this agent; the remaining chapters were read continuously, section by section, by 7 shard readers with an exact ledger preserved; the SVG tags and edge relationships of the 7 figures were also read, without claiming screenshot verification. This “full” denotes only the complete text of the public N3337 draft; it does not mean the paid ISO/IEC 14882:2011 official publication was obtained or read through, nor does it include cited external standards.

Edition, actual reading range, and original sources →

First see which object the rule applies to

“Construction failure does not destroy” is only half the story. For an incomplete non-delegating construction, the complete object's destructor does not run; but each already-constructed member and base class has its own lifetime and needs cleanup. If a resource is only a raw-pointer member, it is not automatically deleted because the outer construction failed.

The sample treats two members as resource handles, logging construction completion with a positive number and destruction with a negative number. The outer constructor body then throws. On entering the catch, the log should be 1, 2, -2, -1; the outer destructor mark does not appear. Member initialization order depends on declaration order, not on the order written in the initializer list.

Put resource responsibility into members that can complete construction

This mechanism explains why RAII must actually form an object, rather than only releasing all resources in the complete class's destructor. If the first member has acquired a resource and then the second member or the constructor body fails, the first member has already independently completed construction, and the language will call its destructor.

The log uses a fixed array to avoid allocating memory or throwing while recording destruction. Real resource-handle destructors should also avoid throwing; another destructor exception escaping during stack unwinding will call terminate. Do not try to report destructor failure by letting an exception keep propagating; design a separate, reportable close operation.

A historical draft cannot impersonate today's full standard

N3337 is a 2012 public working draft, not the paid ISO/IEC 14882:2011 official publication. This lesson uses only the construction, destruction, and exception clauses that were explicitly read, and the example's relevant behavior is consistent from C++11 through C++20.

Historical wording cannot all be applied unchanged today: N3337 still describes dynamic exception specifications and the then-rule that noexcept is not part of the function type; the modern language has changed. When consulting the standard, record the version, stable clause tags, and paragraphs, rather than only a chapter number that can change across versions.

Pitfalls

  • After a constructor fails, you cannot rely on the complete object's destructor to release a raw resource; establish resource ownership in a member type.
  • In a constructor function-try-block handler, already-completed members have already been destroyed; you cannot access them again to do cleanup.

Run an example

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

#include <array>
#include <cassert>
#include <cstddef>
#include <stdexcept>
struct Log {
    std::array<int, 8> values{};
    std::size_t size = 0;
    void add(int value) noexcept { values[size++] = value; }
};
struct Member {
    Log& log;
    int id;
    Member(Log& target, int value) : log(target), id(value) { log.add(id); }
    ~Member() noexcept { log.add(-id); }
};
struct Bundle {
    Member first;
    Member second;
    Bundle(Log& log, bool fail) : first(log, 1), second(log, 2) {
        if (fail) throw std::runtime_error("construction stopped");
    }
    ~Bundle() noexcept { first.log.add(9); }
};
int main() {
    Log failed;
    bool caught = false;
    try { Bundle b(failed, true); }
    catch (const std::runtime_error&) { caught = true; }
    assert(caught && failed.size == 4);
    assert((failed.values == std::array<int, 8>{{1, 2, -2, -1, 0, 0, 0, 0}}));
    Log success;
    { Bundle b(success, false); }
    assert(success.size == 5);
    assert((success.values == std::array<int, 8>{{1, 2, 9, -2, -1, 0, 0, 0}}));
}

Compile locally

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

Expected result

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

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

If the second member throws in its own constructor body, which destructors are called?

Show a reference answer

The first member has already completed construction, so it is destroyed; the second member itself did not complete construction, so the second member's destructor is not called; the outer Bundle is also incomplete. If you still record 2 at the start of the second constructor body and then throw, the log is 1, 2, -1; here 2 only means the constructor body was entered, no longer that construction completed, so the naming of log events should be updated accordingly.

Check the sources

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

Back to the catalog