Constructors, Destructors & Initialization Order
In modern C++, we never guess what the compiler does. We look directly at byte offsets, CPU registers, and object ownership lifetimes.
C++ guarantees that objects are destroyed in the exact reverse order of their construction. If construction order were determined by the initializer list, different constructor overloads could construct members in different orders. The compiler would then have to track the construction order of every object at runtime to determine how to destroy it, violating the zero-overhead principle. By fixing initialization strictly to the class declaration order, destruction order is statically known at compile time.
In the member initializer list, members are constructed directly in-place using their copy or move constructors. In contrast, assigning inside the constructor body { member = value; } first invokes the member's default constructor before the body starts, and then invokes its assignment operator inside the body. This causes unnecessary double-initialization overhead and fails completely for const members, reference members (T&), and types without a default constructor.
You should mark all single-argument constructors (or constructors where all but one argument have default values) explicit by default. This prevents the compiler from performing implicit conversions when passing values to functions or using copy-initialization syntax (MyType obj = value;). You should only omit explicit when implicit conversion is an intentional, ergonomic design feature of the class (such as std::string_view(const char*) or mathematical conversion types).
Interviewer's lens
Object construction in C++ works like building a house: foundation first, framing second, interior fixtures last. Destruction tears it down in exact reverse. Interviewers use this topic to see if you visualize assembly-level object layout or just guess at syntax.
Weak candidates assume members initialize in whatever order they write them in constructor lists, or treat body assignment ({ x = v; }) as genuine initialization. Senior candidates demonstrate three non-negotiables:
- Declaration Order Invariant: Member variables are always constructed in class declaration order, strictly guaranteeing compile-time static destruction order.
- Subobject Assembly Line: Construction flows deterministically from Virtual Base → Direct Base → Member Objects → Derived Body (and exact LIFO reverse during destruction).
- Safety Boundaries: They use
explicitto block unintended implicit conversions and RAII ScopeGuards to ensure automatic rollback on constructor failure.
Under the hood & memory layout
Instantiating an object on the stack simply moves the stack pointer register (RSP) to reserve bytes. At that instant, those bytes hold uninitialized garbage. The C++ runtime then constructs the object through a strict 4-step sequence:
- Virtual Base Subobjects: The most-derived class constructs virtual bases first, ensuring only one shared copy exists.
- Direct Base Classes: Constructed left-to-right in the exact order listed in the inheritance header (
class Derived : public B1, public B2). - Non-Static Data Members: Constructed strictly in the order they are declared in the class header, completely ignoring their order in the initializer list.
- Constructor Body
{}: Finally runs user logic once all underlying subobjects are fully alive and valid.
Destruction mirrors this in exact LIFO (Last-In, First-Out) order: ~Derived body runs first, member fields destroy in reverse declaration order, and base classes tear down last. Assigning to members inside {} forces the compiler to default-construct them first and then overwrite them via assignment operator—incurring a double-initialization penalty that fails outright for const or reference (T&) fields.
Allocating space on the stack (Derived d;) merely moves the stack pointer register (RSP). Initialization executes constructor functions sequentially on that memory before the object reference is exposed.
Members are constructed in the order declared in the class header. If memberA is declared before memberB, memberA is always constructed first. Compilers emit -Wreorder warnings if the constructor initializer list differs from this order.
The member initializer list directly calls T::T(args...) in-place on the allocated memory. Assigning inside { member = val; } first executes T::T() (default constructor) and then T::operator=(val) (copy/move assignment).
Destruction is strictly LIFO (Last-In, First-Out): ~Derived body → ~MemberN → ~Member1 → ~Base body. This ensures dependencies remain valid while higher-level destructors run.
Core concepts & syntax traps
Default, Parameterized & Delegating Constructors
Default, Parameterized & Delegating Constructors
A constructor is an object's factory contract: it transforms raw, uninitialized memory bits into a valid working instance.
- Default & Parameterized: A default constructor takes zero arguments (or provides defaults for all). If you write any custom constructor, the compiler removes its automatic default constructor unless you explicitly restore it with
= default. - Delegating Constructors (C++11): One constructor can delegate work to another constructor in the same class via its initializer list (
: ServerConfig(...)). This centralizes validation in one canonical constructor without duplicate logic. - The Construction Milestone: As soon as the delegated target constructor's closing brace
}finishes, the object is formally fully constructed. If the caller's body throws an exception afterward, the class destructor~T()will run during stack unwinding.
class ServerConfig {
std::string host_;
int port_;
int timeoutSec_;
public:
// Target constructor with complete validation:
ServerConfig(std::string host, int port, int timeout)
: host_(std::move(host)), port_(port), timeoutSec_(timeout) {}
// Delegating constructors:
ServerConfig(std::string host, int port) : ServerConfig(std::move(host), port, 30) {}
explicit ServerConfig(std::string host) : ServerConfig(std::move(host), 8080, 30) {}
};Use delegating constructors to consolidate initialization logic into a single canonical constructor.
Single-Argument Constructors & The explicit Keyword
Single-Argument Constructors & The explicit Keyword
In C++, any constructor callable with a single argument acts as an automatic implicit type-conversion operator by default.
- The Silent Conversion Trap: If a class defines
Buffer(int size), passing the integer42to a function expectingvoid send(Buffer b)compiles without warnings—silently allocating a 42-byte heap buffer behind your back! - The
explicitShield: Prefixing the constructor withexplicitinstructs the compiler to forbid implicit conversions and copy-initialization syntax (Buffer b = 42;). - Enforced Direct Initialization: Callers must explicitly request construction (
Buffer b(42);orBuffer b{42};), eliminating unexpected temporary objects and expensive hidden allocations.
class AccountId {
int id_;
public:
explicit AccountId(int id) : id_(id) {}
};
void verify(AccountId acc);
// verify(105); // COMPILE ERROR: Cannot implicitly convert int to AccountId!
verify(AccountId(105)); // OK: Explicit conversion is clear and intentionalMark all single-argument constructors explicit unless implicit conversion is an intentional part of the class design (e.g., std::string_view).
Member Initializer List vs Body Assignment
Member Initializer List vs Body Assignment
Using the Member Initializer List constructs fields directly in-place. Assigning inside the constructor body {} is not true initialization—it is reassignment after default creation.
Initializer lists are mandatory in three situations:
constdata members: Read-only variables that cannot be reassigned once created.- Reference members (
T&): Aliases that must bind to an existing variable at birth; they can never be reseated. - Types lacking default constructors: Subobjects that cannot be instantiated 'empty' before entering the
{}body.
For standard types like std::string or std::vector, writing name_ = val; inside {} triggers double initialization: the default constructor runs first to allocate an empty buffer, and then the assignment operator destroys it to copy new data. The initializer list executes direct in-place construction in a single step.
class Worker {
const int id_; // MUST use initializer list
std::string& log_; // MUST use initializer list
std::string name_; // Prefer initializer list (avoids double initialization)
public:
Worker(int id, std::string& log, std::string name)
: id_(id), log_(log), name_(std::move(name)) {}
};Always initialize members in the member initializer list; assigning inside the constructor body causes double-initialization overhead and fails for const/reference members.
The #1 C++ Initialization Order Trap: Declaration Order vs List Order
The #1 C++ Initialization Order Trap: Declaration Order vs List Order
Non-static class members are always initialized in the order they are declared in the class definition, completely ignoring their ordering in the member initializer list.
Consider this frequent production bug:
class Bad {
int b_;
int a_;
public:
Bad(int val) : a_(val), b_(a_ * 2) {} // BUG!
};
- The Trap: Because
b_is declared beforea_in the class body,b_is constructed first. - Garbage Evaluation: When
b_(a_ * 2)runs,a_has not been initialized yet.b_reads uninitialized stack garbage. - The Fix: Always order class header declarations to match dependencies, and keep the initializer list in identical sequence. Modern compilers emit a
-Wreorderwarning when these sequences diverge.
// CORRECT: Declare dependent member AFTER its dependency
class Good {
int a_; // Declared 1st -> initialized 1st with val
int b_; // Declared 2nd -> initialized 2nd with a_ * 2
public:
Good(int val) : a_(val), b_(a_ * 2) {}
};Declare member variables in exact order of their dependencies, and write the initializer list in identical order to heed compiler warnings.
Exact Construction & Destruction Order across Inheritance and Composition
Exact Construction & Destruction Order across Inheritance and Composition
C++ executes object construction and destruction like a strict LIFO stack (like a stack of cafeteria trays):
Construction Sequence (Base to Derived):
- Virtual Base Classes: Constructed first in depth-first, left-to-right traversal.
- Direct Base Classes: Constructed left-to-right in inheritance declaration order.
- Member Objects: Constructed in class declaration order.
- Derived Constructor Body
{}: Runs last once all subobjects exist.
Destruction Sequence (Exact Reverse Mirror):
- Derived Destructor Body
~Derived() {}: Cleans up derived state first. - Member Objects: Destroyed in reverse declaration order.
- Direct Base Classes: Destroyed in reverse inheritance order.
- Virtual Base Classes: Destroyed last in reverse order.
This guarantees that while any destructor runs, all base classes and member fields remain 100% valid and accessible.
class Base { public: Base() { /* 1 */ } ~Base() { /* 4 */ } };
class Member { public: Member() { /* 2 */ } ~Member() { /* 3 */ } };
class Derived : public Base {
Member m_;
public:
Derived() { /* 3: Derived body */ }
~Derived() { /* 1: Derived body */ }
};Constructors execute base-to-derived; destructors execute derived-to-base to ensure valid subobjects during cleanup.
Unlock Live Implementation & Interview Scenarios
The C++23 production code, live coding steps, scenario twist solutions, and common UB traps are part of Brute2Best Pro. Module 1 is completely free.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.