Classes, constructors, destructors & this

RoadmapsC++

Scenario

You initialize a member variable based on another member variable inside the constructor's init list, but when you run the program, one of the variables holds random garbage data.

Why did the compiler ignore the order of your initialization list?

Mental model

Think of the class definition (the header) as a blueprint. The compiler builds the walls of the house strictly in the order they appear on the blueprint. The constructor's initializer list just provides the materials; it does NOT dictate the building order.

In C++, member variables are initialized in the exact order they are declared in the class definition, regardless of the order in the constructor's member initialization list. Relying on initialization order when one member depends on another is a classic trap. Furthermore, the initialization list is used to construct objects directly, rather than default-constructing them and then reassigning them inside the constructor body.

Explanation

Constructors set up your object's initial state, and destructors (`~ClassName()`) clean it up. When setting member variables, C++ provides a 'member initializer list' (`: mem1(val1), mem2(val2)`). You should ALWAYS use this instead of assigning values inside the constructor body `{ mem1 = val1; }`.

Why? If you assign in the body, the compiler first default-constructs the member, and then you overwrite it with an assignment. For complex objects (like `std::string` or `std::vector`), this is a double-work performance penalty. For `const` members or reference members, assignment in the body is outright illegal—they must be initialized in the list.

The most dangerous pitfall is the initialization order. Members are initialized in **declaration order** (top-to-bottom in the `class {}` block). If `y` is declared before `x`, but you write `: x(10), y(x)` in the init list, `y` is initialized first, using the uninitialized garbage value of `x`.

The `this` pointer is a hidden parameter passed to all non-static member functions, pointing to the instance the method was called on. It is mainly used to disambiguate member variables from parameters with the same name (e.g., `this->value = value;`).

Code examples

The bug: Initialization order relies on the init-list

#include <iostream>

class Buffer {
    int size;
    int max_size;
public:
    // BUG: 'size' is declared before 'max_size' in the class.
    // Therefore, 'size' is initialized FIRST, using 'max_size' (which is still garbage).
    // Then 'max_size' is initialized.
    Buffer(int cap) : max_size(cap), size(max_size) {}
    
    void printSize() { std::cout << size << '\n'; }
};

int main() {
    Buffer b(10);
    b.printSize(); // Prints a random garbage number, not 10!
    return 0;
}

Because `size` appears before `max_size` in the class body, the compiler initializes `size` first. It uses `max_size`, which hasn't been initialized yet! `size` receives a garbage value.

The fix: Match declaration order and init-list order

#include <iostream>

class Buffer {
    // Declare in the logical order of initialization
    int max_size;
    int size;
public:
    // Init list order matches declaration order.
    // 'max_size' initializes safely, then 'size' initializes.
    Buffer(int cap) : max_size(cap), size(cap) {}
    
    // Using 'this' to resolve naming collisions
    void resize(int size) {
        this->size = size;
    }
};

int main() {
    Buffer b(10);
    b.resize(20);
    return 0;
}

By declaring `max_size` first, we can safely initialize both. Modern compilers will emit a warning (like `-Wreorder`) if your init list order doesn't match your declaration order.

Key points

Common mistakes

Recall questions

Questions & answers

A code review flags the constructor `Entity(int id, int health) : health(health), id(id) {}` where the class declares `int id; int health;`. Is this a bug?

While it is not strictly a bug (the compiler will still initialize `id` then `health`), it triggers compiler warnings (`-Wreorder`) because the init-list order is misleading. If `health` depended on `id`, it would be a critical bug. The list should match the declaration order.

Approach: Understand that the init list order is a visual lie if it doesn't match declaration order.

A candidate writes `std::string str;` in a class, and in the constructor they write `this->str = "Hello";`. How can this be optimized?

It should be moved to the initialization list: `ClassName() : str("Hello") {}`. This prevents the string from being default-constructed (allocating an empty string) and immediately overwritten.

Approach: Identify the performance penalty of assignment vs initialization.

Continue learning

Next: Rule of 3 / 5 / 0 - copy, move, destructor

Next: Operator overloading — common interview cases

Return to C++ Roadmap