A constructor’s member initializer list can mislead you if you read it left to right.
Because the C++ standard says so, data members are not initialized in the order they appear in the member initializer list.
They are initialized in the order they are declared inside the class. If those two orders disagree, the code may compile but behave differently from what you intended.
Three things to take away:
- Members initialize in declaration order, not initializer-list order.
- Reordering the initializer list does not change construction order.
- Write the initializer list in the same order as the class members.
The misleading version
Consider a class that stores a rectangle’s width, height, and area:
#include <iostream>
class Rectangle {
private:
int m_area {};
int m_width {};
int m_height {};
public:
Rectangle(int width, int height)
: m_width { width }
, m_height { height }
, m_area { m_width * m_height }
{
}
void print() const{
std::cout << "Width: " << m_width << '\n';
std::cout << "Height: " << m_height << '\n';
std::cout << "Area: " << m_area << '\n';
}
};
int main(){
Rectangle rectangle { 6, 7 };
rectangle.print();
}
Terminal output on one possible run:
Width: 6
Height: 7
Area: 0
At first glance, this constructor looks reasonable:
Rectangle(int width, int height)
: m_width { width }
, m_height { height }
, m_area { m_width * m_height }
{
}
The initializer list appears to initialize m_width, then m_height, then m_area.
But that is not what actually happens.
The real initialization order
C++ initializes members in the order they are declared inside the class:
private:
int m_area {};
int m_width {};
int m_height {};
So the actual order is:
1. m_area
2. m_width
3. m_height
That means m_area is initialized before m_width and m_height.
This line is therefore broken:
m_area { m_width * m_height }
At the moment m_area is initialized, m_width and m_height have not yet received the constructor arguments.
This is the trap: the member initializer list order does not control member initialization order.
The corrected version
Fix the class by declaring the members in dependency order:
#include <iostream>
class Rectangle {
private:
int m_width {};
int m_height {};
int m_area {};
public:
Rectangle(int width, int height)
: m_width { width }
, m_height { height }
, m_area { m_width * m_height }
{
}
void print() const{
std::cout << "Width: " << m_width << '\n';
std::cout << "Height: " << m_height << '\n';
std::cout << "Area: " << m_area << '\n';
}
};
int main(){
Rectangle rectangle { 6, 7 };
rectangle.print();
}
Terminal output:
Width: 6
Height: 7
Area: 42
Now the declaration order matches the dependency order:
int m_width {};
int m_height {};
int m_area {};
m_width and m_height are initialized first.
Then m_area can safely use them:
m_area { m_width * m_height }
Keep the initializer list in the same order
The best practice is simple: write the member initializer list in the same order as the member declarations.
Good:
class Rectangle {
private:
int m_width {};
int m_height {};
int m_area {};
public:
Rectangle(int width, int height)
: m_width { width }
, m_height { height }
, m_area { m_width * m_height }
{
}
};
Bad:
class Rectangle {
private:
int m_width {};
int m_height {};
int m_area {};
public:
Rectangle(int width, int height)
: m_area { m_width * m_height }
, m_height { height }
, m_width { width }
{
}
};
The second version still initializes in declaration order, but the initializer list visually lies to the reader.
A good compiler warning level can help catch this. GCC and Clang can warn about initializer-list order with -Wreorder.
Prefer direct constructor arguments when possible
If one member depends on constructor arguments, use the arguments directly where it is clearer:
Rectangle(int width, int height)
: m_width { width }
, m_height { height }
, m_area { width * height }
{
}
This avoids depending on another member’s initialization state.
However, declaration order still matters. If members depend on other members, declare the depended-on members first.
Takeaway
Member initializer lists do not determine member initialization order.
C++ initializes data members in the order they are declared inside the class