Constructors and Destructors
How objects come into being and how they clean up — the special member functions you must understand.
Every C++ object has a beginning and an end. The constructor runs at the beginning to put the object in a valid state. The destructor runs at the end to release whatever the object was holding onto. These two functions are the spine of resource management in C++.
Constructors put fields into a valid state
A constructor is a special method named after the class. It runs once, automatically, when an object is created.
The : who_(std::move(who)) part is the member initializer
list. It runs before the constructor's body and initializes
each field. Always prefer the initializer list over assignment
inside the body — it constructs each field directly instead of
default-constructing and then overwriting.
Multiple constructors (overloads)
A class can have several constructors with different signatures:
If you write no constructors, the compiler gives you a default
constructor that default-initializes each field. The moment you
write any constructor, the compiler-generated default goes away.
If you still want it, write Rect() = default;.
The destructor releases resources
The destructor is a method named ~ClassName that takes no
parameters. It runs automatically when an object's lifetime ends —
at the closing brace of its scope for a local, at delete for a
heap object, when the program ends for a global.
Watch the output carefully. Destruction happens in reverse order of construction, on scope exit. This deterministic, automatic cleanup is the magic that makes RAII possible.
Lifecycle of an object
For a local on the stack, "allocate storage" and "deallocate" are
free — just adjusting the stack pointer. For a heap object created
with new, allocation happens first and deallocation happens at
delete.
Why constructors matter for invariants
Recall the Date from the previous chapter. The constructor was
the only way to set the fields. That meant that once a Date
existed, we knew its month and day were sensible. The
constructor's job is to put the object in a state that satisfies the
class invariants. The rest of the class's methods can then assume
the invariants hold.
A first taste of the Rule of Three
If your class manages a resource that the compiler doesn't know how
to copy (a raw new-allocated buffer, an open file handle, a
socket), you usually need three coordinated functions:
- The destructor — releases the resource.
- The copy constructor — defines what it means to copy the object.
- The copy assignment operator — defines what it means to replace an existing object's contents with another's.
This trio is called the Rule of Three. The standard library
containers already implement it correctly, which is the deeper
reason to prefer std::vector and std::string to raw new/delete.
We'll see the modern extension (the Rule of Five) when we cover move semantics. For now, the practical takeaway is: prefer types that already manage their own resources, and you almost never need to write any of these by hand.
Challenge
Write a Stopwatch class with a constructor that takes an initial integer reading (default 0), a tick() method that increases the reading by 1, and a read() const method that returns the current reading. The provided main constructs one with starting value 10, ticks twice, prints 12.
Test your understanding
When does a destructor run for a local stack object?
When the program exits.
Whenever the compiler decides.
At the closing brace of the scope in which the object was created, in reverse order of construction.
When you explicitly call delete on it.
Why prefer a member initializer list over assigning inside the constructor body?
It is the only legal form.
The body cannot reference members.
The initializer list constructs each field directly with the right value, instead of default-constructing it first and then overwriting it. This is more efficient and required for const and reference members.
It causes the constructor to run twice.
Next: how classes can extend other classes — inheritance and the runtime polymorphism that makes interfaces dynamic.