Skip to content
VLSI Mentor

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

What Construction Actually Does

The constructor has been doing the real work for two whole modules without being explained. Chapter 11.1 introduced one as an instrument so that a class could refuse an invalid state, every chapter since has used them, and Chapter 11.9's finished register-field type has one doing the most important thing in the design — establishing the invariant — with its member initializer list named and waved past. That debt is worth paying carefully rather than quickly, because construction is where most of a class's correctness is decided. This chapter does not start with syntax. It starts with a distinction that sounds like pedantry and turns out to be the organising idea for the whole module: obtaining storage and making an object out of that storage are two separate events, performed by different machinery, and a surprising amount of C++ follows from the gap between them. Chapter 10.3 already measured that gap from one side, by intercepting the storage step for a dynamically allocated object so that all four events printed in order. This chapter measures it from the other side, inside an ordinary object with members, and the result settles three questions that later chapters will spend whole chapters on — why initializing a member is not the same as assigning to it, why member order is a property of the declaration rather than of the constructor, and why a resource acquired in a constructor cannot be leaked.

Intermediate17 min readC++Part IFundamentalsConstructors

Part I · Module 12 · Chapter 12.1 · What Construction Actually Does

1. Where C++ Stops Resembling C

This module's organising claim, and it is worth stating before any syntax:

Construction and destruction are language-run events with a guaranteed order.

In C, obtaining memory and making it useful are separate things you do by hand: malloc, then fill in the fields, and if you forget a field nothing objects. In C++ the language takes over both ends — it runs code when an object comes into existence and runs code when it ceases to exist, at moments it chooses, on every path.

That guarantee is the entire mechanism behind Chapter 12.9's RAII, and the rest of this chapter is about what the guarantee actually consists of.

2. Two Events, Not One

Chapter 10.5 §6 made the distinction and did not develop it:

Storage is a region of bytes. An object is something made out of those bytes.

They are separate, and three measurements from earlier chapters show the seams.

Storage can exist with no object in it. Chapter 10.3 §3 intercepted both halves of new:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
new Probe(7):
   [1] storage obtained: 4 bytes
   [2] constructor ran, id=7
delete p:
   [3] destructor ran,  id=7
   [4] storage released

Steps 1 and 4 are storage; steps 2 and 3 are the object. Between 1 and 2 there were four bytes and no Probe.

Storage can outlive its object. Chapter 10.4 §5 measured a mismatched delete skipping two of three destructors — their storage was handed back while their lifetimes had never formally ended.

And the same storage can hold a different object later. Chapter 10.2 §4 built its bug on exactly that: the released stack frame is the next frame handed out.

For an automatic object the storage step is nearly invisible — Chapter 10.2 §2 measured it as a single register adjustment — but it still happens, and it still happens first. (The facility for deliberately constructing an object in storage you already hold is placement new, named in Chapter 10.3 §9 and left to Part II. This chapter needs only that the two steps are distinct.)

3. What Happens Between the Braces

Here is the measurement that makes the rest of the module predictable. Three member objects, each announcing itself, inside a class that also announces itself:

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

struct Member {
    const char* tag;
    explicit Member(const char* t) : tag(t) { std::printf("    member %s constructed\n", tag); }
    ~Member()                               { std::printf("    member %s destroyed\n", tag); }
};

class Owner {
public:
    Owner() : a_("a"), b_("b"), c_("c")
    {
        std::printf("  Owner body runs (all three members already exist)\n");
    }
    ~Owner()
    {
        std::printf("  Owner body of destructor runs (members still alive)\n");
    }
private:
    Member a_;
    Member b_;
    Member c_;
};

int main()
{
    std::printf("creating Owner:\n");
    { Owner o; std::printf("  -- leaving the scope --\n"); }
    std::printf("done\n");
    return 0;
}

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
creating Owner:
    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
done

Four facts, and each one matters later.

All three members are constructed before the constructor body runs. By the time your code between the braces executes, every member already exists as a fully constructed object. The body is not where members are made; it is where you do whatever is left.

The constructor body runs last. It is the final step of construction, not the first.

The destructor body runs first, while every member is still alive. You can still use them.

And the members are destroyed in exact reverse order — c, b, a.

4. So What Does the Constructor Body Do?

If the members already exist when the body starts, something must have constructed them. For Owner it was the initializer list. When there is no initializer list, the compiler still has to do something, and what it does is measurable:

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

class Implicit {
public:
    int         i;            // no default member initializer
    double      d;
    std::string s;            // has its own default constructor
};

int main() {
    Implicit a;                       // the compiler's own default constructor
    std::printf("int i   = %d  (indeterminate?)\n", a.i);
    std::printf("double d= %f\n", a.d);
    std::printf("string s= \"%s\" size %zu\n", a.s.c_str(), a.s.size());
    std::printf("sizeof = %zu\n", sizeof(Implicit));
    return 0;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warnings: 0
int i   = 1  (indeterminate?)
double d= 0.000000
string s= "" size 0
sizeof = 40

Read the first line again: the int held 1. Not zero — an actual leftover value. This is stronger evidence than Chapter 10.3 §5's new int, which read 0 on every one of three deliberate attempts; here the garbage is visible.

What the compiler's default constructor did, member by member:

MemberWhat happened
int inothing — the bytes were left as they were, so reading it is undefined
double dnothing — it happened to read 0.0
std::string sits own default constructor ran, producing a valid empty string

The implicit default constructor default-initializes each member: it calls a constructor for members that have one, and leaves built-in types alone.

And 0 warnings for reading the indeterminate int, which is Chapter 10.3 §5's asymmetry again — the compiler warns about an uninitialized local (Chapter 3.8 measured -Wuninitialized) and cannot see this one, because from its point of view a was initialized by a constructor.

That is the whole argument for the habit Chapter 11.2 §8 recommended: give every data member a default member initializer, or initialize it in a constructor. Chapter 12.4 owns the rules about which constructors exist; this section is here because it shows what storage without an object looks like from the inside — the bytes were there, something had to make an int out of them, and for i nothing did.

5. The Shape of Construction

Putting §2's storage step together with §3's object steps, construction of any object is:

1. Storage is obtained — a stack adjustment, a slot in the enclosing object, or operator new (Chapter 10.3).

2. Base classes are constructed, if any. Module 16's subject, named here for completeness.

3. Members are constructed, in declaration order. §3 measured it.

4. The constructor body runs.

And the object's lifetime begins when step 4 completes — which is Chapter 10.5 §1's definition, now with the steps filled in.

Destruction is the exact reverse:

1. The destructor body runs (§3: members still alive).

2. Members are destroyed, in reverse declaration order.

3. Base classes are destroyed.

4. Storage is released.

The symmetry is not a coincidence or an aesthetic choice; §7 is why it has to be that way.

6. The Order Is the Declaration's, Not the List's

One result from §3 deserves separating out, because it is the single most common surprise in this module.

Owner's initializer list was : a_("a"), b_("b"), c_("c") and the members are declared a_, b_, c_, so the two orders agreed and the measurement could not distinguish them. They do not have to agree, and when they disagree the declaration order wins. Chapter 12.8 measures that directly, including the compiler warning that catches the mismatch.

The reason is forced rather than chosen: destruction must be the reverse of construction (§5), and a destructor has no idea which constructor built the object. If construction order followed the initializer list, two different constructors could build the members in two different orders and the single destructor could not reverse both. So the order has to come from something every constructor and the destructor share — the declaration.

7. One Distinction, Three Consequences

This is why the chapter exists rather than starting at Chapter 12.2. The storage-versus-object distinction is not trivia; three later chapters are each a consequence of it.

Initializer lists (Chapter 12.3). If members are constructed before the body, then anything you write in the body is necessarily happening to an already-constructed member — which means it is an assignment, not an initialization. For an int that is a wasted store. For a const member, a reference member, or a member with no default constructor, there is no valid "already-constructed" state to assign over, so the body cannot do the job at all. Chapter 12.3 measures all three.

Member order (Chapter 12.8). Because order must be shared by every constructor and the destructor, it belongs to the declaration — §6.

RAII (Chapter 12.9). This is the one the module is for. If the constructor body is the last step of construction, then an object only exists once its constructor has finished — so if a constructor acquires a resource, the object's existence is evidence that the acquisition happened. And if the destructor runs whenever the object's lifetime ends, on every path, then the release happens whenever the acquisition did. Chapter 10.8 measured the consequence: 3 leaks and 96 bytes became 0 leaks and 0 bytes with no change to the control flow.

All three are the same fact, which is why the frozen description of this chapter says the distinction "makes initializer lists, member order and RAII all one idea."

8. What to Actually Do

Think of construction as four steps — storage, bases, members, body — and destruction as their exact reverse.

Expect every member to exist by the time the body runs. The body is for what is left over, not for making members.

Initialize members in the initializer list, not the body — Chapter 12.3, and §7 is the reason.

Give every member a default member initializer unless a constructor will set it. Measured: the implicit default constructor left an int reading 1, with 0 warnings.

Know that order comes from the declaration, not from the initializer list.

Do not rely on the implicit default constructor for built-in members. It does nothing to them.

Never let a constructor finish with the invariant violated. Throw, or narrow the types.

And do not use a validity flag — it restores the invalid state the type was meant to forbid.

9. Mistakes Worth Making Once

Assuming the constructor body is where members are created. They already exist.

Assigning to members in the body when you meant to initialize them — Chapter 12.3 measures the cost.

Assuming the initializer-list order controls construction order. It does not; Chapter 12.8 measures it.

Trusting the implicit default constructor to zero built-in members. Measured: int i read 1.

Expecting a warning for reading it. 0 warnings — the compiler sees a constructed object.

Writing an init() method called by convention. Chapter 10.8 measured what happens to code that must be remembered.

Returning a half-built object with an ok flag. Every later operation must check it.

Expecting the destructor body to run after the members are gone. It runs first, with members alive.

10. Interview Relevance

"What happens, in order, when an object is constructed?" Storage, then base classes, then members in declaration order, then the constructor body — and the lifetime begins when the body completes. Destruction is the exact reverse. This is a very common question and the ordering is what is being checked.

"Are members initialized before or after the constructor body runs?" Before. The strong answer adds the consequence: anything in the body is therefore an assignment to an existing member, which is why initializer lists exist.

"What does the compiler's default constructor do?" Default-initializes each member — runs a constructor for members that have one, and leaves built-in types untouched. The measured int i = 1 with 0 warnings is the detail that shows you have actually tried it.

"Why is member construction order the declaration order rather than the initializer-list order?" Because destruction must reverse construction, and the destructor does not know which constructor ran — so the order must be shared by all of them.

"What happens if a constructor throws?" Already-constructed members are destroyed in reverse order, the storage is released, and the object never existed — which is what makes acquiring a resource in a constructor safe.

11. What You Should Be Able to Say Now

  1. Construction and destruction are language-run events with a guaranteed order — the module's thesis.
  2. Storage and an object are different things; Chapter 10.3's four steps separate them — 1 and 4 storage, 2 and 3 object.
  3. Measured: all three members were constructed before the constructor body ran.
  4. Measured: the destructor body runs first, with members still alive, then members are destroyed c, b, a — exact reverse.
  5. The four steps: storage → bases → members in declaration order → body; and the lifetime begins when the body completes.
  6. Destruction reverses all four.
  7. Measured: the implicit default constructor left int i reading 1 and double d reading 0.0, while std::string ran its own constructor.
  8. So it default-initializes: a constructor for members that have one, nothing for built-ins.
  9. Measured: 0 warnings for reading it — the compiler sees a constructed object (Chapter 10.3 §5's asymmetry).
  10. Order comes from the declaration, not the initializer list — because the destructor must reverse it without knowing which constructor ran.
  11. One distinction, three consequences: initializer lists (12.3), member order (12.8), RAII (12.9).
  12. A constructor cannot report partial failure — throw, or narrow the types; never a validity flag.
  13. If a constructor throws, built members are destroyed in reverse and the object never existed (Chapter 12.7 §8).

12. Exercises

Exercise 1 — predict the trace. A class has members x_, y_ with printing constructors and destructors, plus printing constructor and destructor bodies. Write the exact output for creating and destroying one object in a block.

Exercise 2 — the four steps. List them for Owner o; inside a function, and say which step the object's lifetime begins at.

Exercise 3 — what did the compiler do? For class C { int i; double d; std::string s; }; and C c;, say what happened to each member and which reads are undefined.

Exercise 4 — why no warning? Explain why reading c.i above draws no diagnostic while reading a local int x; does.

Exercise 5 — the order argument. Explain, without using the word "standard", why member construction order cannot follow the initializer list.

Exercise 6 — assignment or initialization? For C() { s_ = "hi"; } with std::string s_, say how many operations occur and which one was avoidable.

Exercise 7 — the three consequences. State how the storage/object distinction explains each of: initializer lists, member order, RAII.

Exercise 8 — partial failure. A colleague's constructor sets ok_ = false when given bad input. List two problems, citing Chapter 11.5.

13. Summary

  • Construction and destruction are language-run events with a guaranteed order — where C++ stops resembling C.
  • Storage and an object are separate; Chapter 10.3's four steps show the seam.
  • Measured: members are constructed before the body; the destructor body runs first; members are destroyed in exact reverse.
  • Four steps: storage → bases → members (declaration order) → body. The lifetime begins when the body completes; destruction reverses all four.
  • Measured: the implicit default constructor left an int reading 1 and a double reading 0.0, while std::string ran its own constructor — and 0 warnings for reading the garbage.
  • Order belongs to the declaration, because the destructor must reverse it without knowing which constructor ran.
  • One distinction explains three chapters: initializer lists, member order, RAII.
  • A constructor must throw or narrow — never finish invalid, never use a validity flag.
  • A throwing constructor destroys what it built, which is what makes resource acquisition there safe.

The shape of construction is now established and not one constructor has actually been written in this chapter — Owner's existed only to print, and every class in Module 11 borrowed one as an instrument. The next chapter writes them properly, and the question it has to answer is less about syntax than it looks. A type usually has more than one legitimate starting point: a register field might be described by an offset and a width, or by a first and last bit, or copied from an existing field with one value changed. Each is a different way the caller happens to have the data, each should produce the same checked invariant, and deciding which of them deserve to exist is a design decision with consequences — one of which Chapter 12.5 will show letting an unrelated integer silently turn into your type.

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.