Skip to content
VLSI Mentor

C++ Part I · Module 12 · Constructors and Destructors

Construction and Destruction Order

Chapter 12.1 claimed that destruction is the exact reverse of construction, and the traces since have shown the two ends of that claim without examining the middle: a destructor body running before its members are destroyed, and members coming apart in the opposite order from the one they were built in. The middle is where the useful content is, for two reasons. The first is that the order members are constructed in is decided by something other than where you wrote their initializers, which is a surprise to most people the first time they hit it and produces a bug that is genuinely hard to see — one member initialized from another that does not exist yet. The compiler has a warning for exactly this, and it is a warning rather than an error, which means a codebase can carry the bug indefinitely. This chapter writes an initializer list in deliberately reversed order and measures what actually happens. The second reason is that the reversal of destruction is not a tidy symmetry chosen for elegance; it is forced, and the argument for why it has to work this way rather than the way it appears to is short and worth following, because it also explains why the order cannot be per-constructor. That guarantee is what the next chapter's idiom rests on entirely.

Intermediate17 min readC++Part IFundamentalsConstructors

Part I · Module 12 · Chapter 12.8 · Construction and Destruction Order

1. The Baseline

Chapter 12.1 §3 measured the shape:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    member a constructed
    member b constructed
    member c constructed
  Owner body runs (all three members already exist)
  -- leaving the scope --
  Owner body of destructor runs (members still alive)
    member c destroyed
    member b destroyed
    member a destroyed

Members a, b, c up; members c, b, a down. That example had its initializer list in the same order as its declarations, so it could not distinguish which one was responsible. §3 distinguishes them.

2. The Full Order

For any object:

Construction

  1. Base classes, in declaration order (Module 16)
  2. Members, in declaration order
  3. The constructor body

Destruction

  1. The destructor body
  2. Members, in reverse declaration order
  3. Base classes, in reverse declaration order

Chapter 12.7 owns when step 1 of destruction begins; this chapter is about steps 2 and 3.

3. The Measurement That Matters

Members declared a_, b_, c_. Initializer list written deliberately backwards:

Azvya Education Pvt. Ltd.VLSI Mentor
reorder.cpp — complete program
#include <cstdio>

struct M { const char* t; explicit M(const char* x) : t(x) { std::printf("  %s\n", t); } };

class C {
public:
    // members declared a_, b_, c_ — but listed here in REVERSE
    C() : c_("c listed first"), b_("b listed second"), a_("a listed third") {}
private:
    M a_; M b_; M c_;
};

int main() { std::printf("initializer list order: c, b, a\nactual construction order:\n"); C x; return 0; }

The compiler says:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: initializer order does not match the declaration order [-Wreorder-ctor]

And the program says:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initializer list order: c, b, a
actual construction order:
  a listed third
  b listed second
  c listed first

Members are constructed in declaration order. The initializer list's order is ignored.

The list says what each member is initialized with. It does not say when.

4. Why This Is a Bug and Not a Curiosity

On its own, a mismatched order is harmless — three members get built in a different sequence than the text suggests and the result is identical. It becomes a bug the moment one member's initializer reads another member:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Window {
    public:
        Window(int lo, int hi) : span_(hi_ - lo_), lo_(lo), hi_(hi) {}
        //                       ^^^^^^^^^^^^^^^ reads hi_ and lo_ first
    private:
        int span_;      // declared FIRST, so built FIRST
        int lo_;
        int hi_;
    };

span_ is declared first, so it is constructed first — from hi_ and lo_, which do not exist yet. Chapter 3.8's indeterminate values, and Chapter 12.1 §4 measured an int member reading 1 in exactly this situation, with 0 warnings for the read itself.

Two things make this dangerous:

-Wreorder-ctor is a warning, not an error. A codebase can carry it for years. It is also only emitted when the orders differ — a member initialized from a later member in matching order produces no warning at all, because there is nothing out of order to notice.

And the symptom is a plausible wrong value, not a crash. Chapter 10.7 §4 measured the general version of why that is the expensive kind: a plausible number passes review.

5. Base Classes, Measured

The frozen description of this chapter includes bases, so they are measured rather than asserted — and kept brief, because Module 16 owns inheritance.

Two base classes and two members, each announcing itself:

Azvya Education Pvt. Ltd.VLSI Mentor
bases.cpp — the type
struct A { A() { std::printf("  + base A\n"); } ~A() { std::printf("  - base A\n"); } };
struct B { B() { std::printf("  + base B\n"); } ~B() { std::printf("  - base B\n"); } };

struct D : A, B {
    D() : m1_("m1"), m2_("m2") { std::printf("  D body\n"); }
    ~D() { std::printf("  ~D body\n"); }
    M m1_, m2_;
};

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
creating D:
  + base A
  + base B
  + member m1
  + member m2
  D body
  -- leaving scope --
  ~D body
  - member m2
  - member m1
  - base B
  - base A

The full five-step order, confirmed in both directions. Bases first in declaration order, then members in declaration order, then the body; and on the way down, the body, then members reversed, then bases reversed. base A was constructed first and destroyed last.

Two consequences worth keeping.

You cannot interleave. There is no way to construct a base, then a member, then another base. All bases precede all members, which matters when a member's initializer wants to call something on a base — that is fine, because the base is already complete.

And the reasoning is §6's, one level out. A derived class may depend on its base being fully built — it may call a base member function from its own constructor body — so the base goes first and is torn down last. Same dependency argument, same forced conclusion.

(Module 16 covers what inheritance is for and the access rules; Chapter 16.5 is construction and destruction through a hierarchy, where this ordering acquires its real consequences.)

6. Why Destruction Must Be the Reverse

Not elegance. Two arguments, and the second is the one that forces the declaration-order rule as well.

Dependency. If b_ was constructed after a_, then b_ is the one permitted to depend on a_ existing — it may hold a reference to it, register itself with it, or have been initialized from it. So b_ must be taken apart while a_ is still valid. Reverse order is the only sequence that guarantees this for every possible dependency, which is why the language guarantees it unconditionally rather than trying to work out which members actually depend on which.

And the destructor does not know which constructor ran. This is the decisive argument, and it is the answer to "why not let the initializer list decide". A type may have five constructors, each with a different initializer list. It has exactly one destructor. If construction order followed the list, then five constructors could produce five different construction orders, and the single destructor would have no way to reverse the one that actually happened — it would need to know which constructor built this particular object, and nothing records that.

So the order has to come from something every constructor and the destructor share. The declaration is the only such thing.

Chapter 12.1 §6 stated this and this is the full argument.

7. The Reversal in the Traces

The same guarantee showing up three ways in measurements already taken.

Nested resources. Chapter 12.9's measurement, cited forward:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    acquire outer      (live=1)
    acquire inner      (live=2)
    release inner      (live=1)
    release outer      (live=0)

Inner released before outer. If a lock were acquired first and a buffer second, the buffer is released while the lock is still held — which is usually exactly what you need.

Partial construction. Chapter 12.7 §8 measured a throw during the second of three members: only a had been built, and only a was destroyed. Reverse order over what was actually built, which is the same rule applied to an incomplete sequence.

Static objects. Chapter 12.7 §4 measured, at program exit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  - static local: first call only
  - static: global

The global was constructed first and destroyed last. Reverse order again.

One honest exception, and it is a real one. Across different translation units, the construction order of static objects is not specified — so "destruction is the reverse of construction" remains true and stops being useful, because you did not know the construction order either. That is the static initialization order problem, named in Chapter 7.4 and Chapter 10.5 §5, and it belongs to Module 24. Within a single translation unit, and for members of one object, the order is fully determined.

8. What to Do About the Order

Declare members in the order they depend on each other — anything a later member needs should be declared earlier. Then the declaration order and the dependency order agree and there is nothing to get wrong.

Write the initializer list in declaration order. It does not change behaviour; it stops the text misleading the next reader, and it keeps -Wreorder-ctor silent so that a real occurrence is visible.

Treat -Wreorder-ctor as an error in your build. It costs nothing and it catches the §4 bug.

Prefer not to initialize one member from another at all. Compute the value from the parameters instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    Window(int lo, int hi) : span_(hi - lo), lo_(lo), hi_(hi) {}
    //                            ^^^^^^^^ parameters, not members — order-independent

That one change makes the whole question disappear, and it is the fix to prefer over reordering the declarations.

And remember Chapter 9.8 §8's separate constraint. Declaration order also determines layout and padding — measured there as 16 bytes versus 12 for the same four members. When the two concerns conflict, dependency wins over padding, because one is correctness and the other is bytes.

9. What to Actually Do

Declare members in dependency order — earlier members first.

Write the initializer list in declaration order, so the text matches reality.

Enable -Wreorder-ctor as an error.

Initialize members from parameters, not from other members. It removes the whole hazard.

Declare a lock before what it protects, so the lock is released last.

Expect bases before members, and bases destroyed last.

Prefer composition for multiple resources — three self-managing members and no destructor.

Do not rely on cross-translation-unit static order (Module 24).

And when dependency order conflicts with Chapter 9.8's padding advice, dependency wins.

10. Mistakes Worth Making Once

Assuming the initializer list controls construction order. Measured: declaration order won and the list was ignored.

Initializing one member from another declared later. Indeterminate read; Chapter 12.1 §4 measured an int reading 1.

Ignoring -Wreorder-ctor. It is a warning, so a codebase can carry the bug for years.

Assuming the warning catches every case. A member initialized from a later member in matching order produces none.

Expecting per-constructor ordering. One destructor cannot reverse five different orders.

Declaring a lock after the data it protects. The data is then released while unprotected.

Writing one destructor for three resources instead of three self-managing members.

Relying on static destruction order across translation units. Module 24.

Reordering members for padding without checking dependencies. Correctness before bytes.

11. Interview Relevance

"In what order are members constructed?" Declaration order — not initializer-list order. The strong answer adds that the compiler warns on a mismatch (-Wreorder-ctor) and that the warning is not an error.

"Why declaration order rather than the list?" Because a type has many constructors and one destructor, and the destructor must reverse the order without knowing which constructor ran — so the order must be shared by all of them. This is the question that separates memorisation from understanding.

"Why is destruction the reverse?" Dependency: a later-constructed member may depend on an earlier one, so it must be destroyed while the earlier one is still valid.

"What is the full order including base classes?" Bases in declaration order, then members in declaration order, then the body; destruction exactly reversed.

"What goes wrong if you initialize span_ from lo_ and hi_?" If span_ is declared first it reads indeterminate values. The better answer gives the structural fix — initialize from the parameters — rather than reordering.

"Does this hold if the constructor throws?" Yes, over whatever was actually built. Measured in Chapter 12.7 §8.

12. What You Should Be Able to Say Now

  1. Construction: bases in declaration order → members in declaration order → body. Destruction: body → members reversed → bases reversed.
  2. Measured: with the list written c, b, a and members declared a_, b_, c_, construction was a, b, c — declaration order won.
  3. The compiler warned: initializer order does not match the declaration order [-Wreorder-ctor].
  4. The list says what, not when.
  5. It becomes a bug when one member's initializer reads another — Chapter 12.1 §4 measured an int reading 1.
  6. The warning is not an error, and it does not fire when a member is initialized from a later member in matching order.
  7. Why declaration order: a type has many constructors and one destructor, which must reverse the order without knowing which constructor ran.
  8. Why reverse destruction: a later member may depend on an earlier one, so it must go first.
  9. Measured, nested resources: release inner before release outer.
  10. Measured, partial construction: only what was built is destroyed, in reverse (Chapter 12.7 §8).
  11. Measured, statics at exit: the global constructed first was destroyed last.
  12. Across translation units the construction order is unspecified — Module 24.
  13. The fix for the hazard is to initialize from parameters, not from other members.
  14. Prefer composition — self-managing members and no destructor (Chapter 13.11).

13. Exercises

Exercise 1 — predict. Members declared x_, y_, z_; list written : z_(3), x_(1), y_(2). Give the construction order, the destruction order, and the diagnostic.

Exercise 2 — find the bug. Window(int lo, int hi) : span_(hi_ - lo_), lo_(lo), hi_(hi) {} with span_ declared first. Say what span_ holds and why no warning mentions the read.

Exercise 3 — two fixes. Give both the reorder fix and the structural fix for Exercise 2, and say which you prefer and why.

Exercise 4 — the argument. Explain, without citing the standard, why construction order cannot follow the initializer list.

Exercise 5 — nesting. A type holds a lock and a buffer. Give the declaration order you want and say what is released first.

Exercise 6 — partial. Members a, b, c; b's constructor throws. List every destructor that runs, and say whether the object's own destructor runs.

Exercise 7 — conflict. Dependency order says char flag; double value; and Chapter 9.8 says the reverse for padding. Say which wins and why.

Exercise 8 — no destructor. A type needs a file handle, a buffer and a lock. Describe the design in which you write no destructor at all.

14. Summary

  • Bases, then members, both in declaration order, then the body — and destruction exactly reversed.
  • Measured: a list written c, b, a built a, b, c — declaration order wins, and the list says what, not when.
  • -Wreorder-ctor catches the mismatch and is a warning, so it can be carried for years.
  • The bug is a member initialized from a later member — an indeterminate read, with no warning for the read.
  • Declaration order is forced because many constructors share one destructor, which must reverse without knowing which ran.
  • Reverse destruction is forced by dependency — a later member may depend on an earlier one.
  • Measured three ways: release inner before release outer; partial construction unwinding only what was built; the first-constructed static destroyed last.
  • Cross-translation-unit order is unspecified (Module 24).
  • Initialize from parameters, declare locks before what they protect, and prefer composition so you write no destructor.

Everything needed is now in place, and it has been in place for three chapters without being assembled. A constructor runs on every path that creates an object and cannot be skipped. A destructor runs on every path that destroys one, including paths created by exceptions, and including the partial case where construction never completed. Members are torn down in the exact reverse of their construction, automatically. Each of those is a guarantee rather than a convention, and together they answer the question Chapter 10.9 posed and could not resolve — which code is responsible for releasing this, and when. The next chapter is the answer written down as a technique, with the measurement that makes the case: the same eight resource acquisitions across five different exit paths, released three times out of eight by hand and eight times out of eight by a destructor.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.