Hello! Welcome back to our C++ journey.
In our last lesson, we demystified the this pointer, establishing it as the hidden first argument to every non-static member function. We saw how the platform's ABI dictates that this pointer (the object's address) is passed in a specific register (RCX on Windows, RDI on Linux) to provide the function with its object context.
Today, we'll examine two of the most important member functions that every C++ programmer must master: constructors and destructors. They are the bookends of an object's life. Our learning outcome is to implement constructors and destructors, and trace their execution order for member and base class objects. This is fundamental to managing resources correctly in C++ and understanding how objects are built and torn down at a low level.
The Object Lifecycle: Construction and Destruction
In C, you manage the lifecycle of a struct manually. You might malloc memory, then call an init_my_struct() function to set its initial state. When you're done, you call a destroy_my_struct() function to free resources, and then free the memory.
C++ automates and formalizes this process with two special member functions:
- Constructor: A function that is automatically called when an object of a class is created. Its primary job is to initialize the object, ensuring it starts in a valid, usable state. A constructor has the same name as the class and has no return type.
- Destructor: A function that is automatically called just before an object is destroyed. Its job is to clean up any resources the object acquired during its lifetime (e.g., freeing memory, closing files, releasing locks). A destructor's name is the class name preceded by a tilde (
~) and it takes no parameters and has no return type.
Let's see this in action:
#include <iostream>
class Tracer {
public:
// Constructor
Tracer() {
std::cout << "Tracer object created at address: " << this << std::endl;
}
// Destructor
~Tracer() {
std::cout << "Tracer object at address: " << this << " is being destroyed." << std::endl;
}
};
int main() {
std::cout << "Entering main..." << std::endl;
{
std::cout << " Entering a new scope..." << std::endl;
Tracer t1; // t1 is created on the stack here
std::cout << " Exiting scope..." << std::endl;
} // t1 goes out of scope and is destroyed here
std::cout << "Back in main." << std::endl;
Tracer* t2 = new Tracer(); // t2 is created on the heap
delete t2; // t2 is explicitly destroyed here
std::cout << "Exiting main..." << std::endl;
return 0;
}
When you compile and run this, you'll see the constructor and destructor messages printed exactly when the objects' lifetimes begin and end. This automatic cleanup is a cornerstone of C++ and the basis for a powerful idiom called RAII (Resource Acquisition Is Initialization), which we will cover in depth later.
Order of Operations: The "C++ Onion"
Things get more interesting when objects are composed of other objects, or when they inherit from base classes. The order in which constructors and destructors are called is strictly defined and crucial for correctness.
Imagine an object as an onion, with layers.
- Inheritance forms the core layers (
Baseclass is inside theDerivedclass). - Member objects are components within a layer.
The rule is: Construction happens from the inside out. Destruction happens from the outside in.

Let's trace this for both composition (has-a) and inheritance (is-a).
-
Composition (A class has a member object):
- Construction: Member objects are constructed first, in the order they are declared in the class. Then, the body of the container class's constructor is executed.
- Destruction: The body of the container class's destructor is executed first. Then, member objects are destroyed in the reverse order of their declaration.
-
Inheritance (A class inherits from a base class):
- Construction: The base class constructor is called first. Then, the derived class's constructor body is executed.
- Destruction: The derived class's destructor body is executed first. Then, the base class destructor is called.
The logic is that a container or derived class cannot be considered "ready" until its constituent parts (members or its base) are fully constructed.
To see a fantastic visualization and deep dive into this process, the following talk is invaluable. It discusses not just the order, but what the compiler is doing during this process, including how the object's identity changes during construction.
C++ Under the Hood: Internal Class Mechanisms - Chris Ryan - CppCon 2024
The following talk from CppCon by Chris Ryan, titled 'C++ Under the Hood: Internal Class Mechanisms', provides an excellent explanation of the object lifecycle.
Please watch from 02:27 to 11:29. Focus on these key points: The C++ Onion (02:27 - 03:42): Grasp the core analogy of 'constructed from the inside out, destroyed from the outside in'. Construction Trace (05:31 - 09:08): Follow the step-by-step construction of a derived object. Pay close attention to how it drills down to the most base class, constructs it, sets its vtable pointer, and then works its way back out. Destruction Trace (09:08 - 11:29): Observe the reverse process. Notice how the vtable pointer is reset at each stage of destruction. This is a critical safety mechanism to prevent calls to functions on already destroyed parts of an object.
A crucial takeaway from that video is what happens to the object's dynamic type. During the execution of a Base class constructor, the object is a Base object. Its vtable pointer points to Base's vtable. Only after the Base constructor completes does the vtable pointer get updated to point to the Derived vtable. This is why calling a virtual function from a base class constructor resolves to the base version, not a derived override—a key difference from languages like Java or C#.
Implementation: Initialization Lists vs. Assignment
Now let's turn to how we should implement constructors. There are two ways to give member variables their initial values: assignment inside the constructor body, or using a member initializer list.
class MyClass {
std::string name;
int value;
public:
// Option 1: Assignment in constructor body
MyClass(const std::string& n, int v) {
name = n; // Assignment
value = v; // Assignment
}
// Option 2: Member Initializer List (Preferred)
MyClass(const std::string& n, int v) : name(n), value(v) {
// Constructor body is now empty
}
};
While these may look similar, they are mechanically very different.
- Assignment: The member variable is first default-constructed before the constructor body runs. Then, the assignment operator (
=) is called inside the body. This is a two-step process: default construction + assignment. - Initializer List: The member variable is directly initialized with the value you provide. This is a one-step process: copy-construction (or move-construction).
For simple types like int, the compiler can optimize away the difference. But for class types like std::string or your own classes, the assignment method is inefficient. It can create and immediately discard a temporary object, or perform a default initialization (like allocating memory) that is immediately undone by the assignment.
The following video demonstrates this performance difference perfectly.
Member Initializer Lists in C++ (Constructor Initializer List)
This video from The Cherno, 'Member Initializer Lists in C++', clearly explains why initializer lists are superior to assignment.
Watch from 03:11 to the end (07:17). The video provides a practical example showing how assignment can cause a member object to be constructed twice, while the initializer list constructs it only once. This is a critical performance and correctness concept.
Furthermore, there are two situations where you must use an initializer list:
- Initializing
constmember variables. - Initializing reference member variables.
Neither of these can be assigned to after they are created, so they must be initialized in the list.
The Rule of Order in Initializer Lists
There's one final, crucial detail: Members are initialized in the order they are declared in the class definition, NOT in the order they appear in the initializer list.
Writing them in a different order in the list can hide dangerous bugs where one member is used to initialize another before it has been initialized itself.
The C++ FAQ is an authoritative source for best practices. This section explains the initialization order rule with a concise but powerful example of what can go wrong.
Read the section titled 'How should initializers be ordered in a constructor’s initialization list?'. Pay close attention to the example code with classes X, Y, and Z. Trace why y_ is used before it is initialized, and how matching the list order to the declaration order would have made this bug obvious.
Always list your initializers in the same order as the member declarations to avoid these subtle and dangerous bugs.
Test your understanding!
Given the following C++ code:
#include <iostream>
struct Wheel {
Wheel() { std::cout << "Wheel constructed.\n"; }
~Wheel() { std::cout << "Wheel destructed.\n"; }
};
struct Engine {
Engine() { std::cout << "Engine constructed.\n"; }
~Engine() { std::cout << "Engine destructed.\n"; }
};
struct Car : public Engine {
Car() { std::cout << "Car constructed.\n"; }
~Car() { std::cout << "Car destructed.\n"; }
private:
Wheel front_left;
Wheel front_right;
};
int main() {
Car my_car;
}
In what order will the "constructed" and "destructed" messages appear on the console?
Show answer
The output will be:
Construction (Inside-out):
- Engine constructed. (Base class first)
- Wheel constructed. (First member)
- Wheel constructed. (Second member)
- Car constructed. (Derived class constructor body last)
Destruction (Outside-in):
- Car destructed. (Derived class destructor body first)
- Wheel destructed. (Second member, reverse order)
- Wheel destructed. (First member, reverse order)
- Engine destructed. (Base class last)
Conclusion
We've covered the essential mechanics of an object's birth and death in C++. Understanding this precise, deterministic order is not just academic; it's the key to writing correct, efficient, and safe C++ code, especially when managing resources.
Key Takeaways:
- Constructors initialize objects, and destructors clean them up.
- Construction Order: Base classes -> Member variables (in declaration order) -> Constructor body. This is the "inside-out" rule.
- Destruction Order: Destructor body -> Member variables (in reverse declaration order) -> Base classes. This is the "outside-in" rule.
- During base class construction, the object's dynamic type is that of the base class.
- Always prefer member initializer lists over assignment in the constructor body for performance and correctness.
- The order of members in the initializer list should always match their declaration order in the class definition.
In our next lesson, we'll build directly on this foundation. What happens when an object is created not from scratch, but as a copy of another object? This will lead us to the "Rule of Three/Five" and a deep dive into copy constructors, move constructors, and assignment operators, which are essential for controlling how your objects are passed around in a program.