When a constructor is executed, it has two opportunities in which to provide the member variables of an object with values. The first opportunity comes from a list of members which comes between the parameter list of the constructor and the curly brace which begins the constructor’s body. The second opportunity is given to the constructor by assignments of values within the body of the constructor. While both opportunities occur, the former is referred to by the name member initializer list, and it is often the appropriate context in which to assign default values to data members.
A member initializer list is designated with a colon that immediately precedes it. The members within the list are all named, and each one has a braced or parenthesized initializer to follow it. A constructor is able to use this list for every single one of its members.
#include <iostream>
// Define the members that belong to this type
class Point
{
private:
int x{};
int y{};
public:
Point(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
std::cout << "Point ready\n";
}
void print()
{
std::cout << x << ',' << y << '\n';
}
};
int main()
{
Point p{ 3, 5 };
p.print();
return 0;
}
This constructor, Point(int xVal, int yVal), uses the member initializer list immediately after the parameter list ends and the body has not yet been begun. The colon is present, and then the member name for the first member, x, has its value provided via the member initializer { xVal }; a comma follows this, and the second member, y, receives a value via its member initializer { yVal }. Only after the member initializer list is completed does the body of the constructor execute, printing "Point ready". Subsequent calls to print show that the members contain the correct values for x and y.
This same constructor, shown written with an assignments-in-the-body style, shows a different order of operations. When a constructor, like this one, relies on assignment for member variable values, the members are first default-initialized, setting them to zero in this case where the members are ints, before the assignment takes place. A member initializer list can skip this default-initialization step and has the member constructed with the appropriate value from the beginning.
The member initializer list does not have to be in the same order as the members appear in the class definition. The compiler knows which members to initialize first. That is, the data members are always initialized in the order they are declared, not the order they appear in the initializer list.
#include <iostream>
class Tracer
{
// Members below this label are private
private:
int first{};
int second{};
public:
Tracer(int a, int b)
: second{ b }
, first{ a }
{
std::cout << "body\n";
}
};
int main()
{
Tracer t{ 10, 20 };
return 0;
}
As can be seen, even though second is named before first on the initializer list, first is actually initialized first, as it was declared first in the class. The body of the constructor does not run until both members are finished being initialized. Because of this, relying on the order of the members listed on the initializer list is considered an error in the same way that relying on the order written on the definition of the constructor would be considered an error. The data members will always initialize in the order they were declared within the class, regardless of what order they appear on the list of members.
Some member data members cannot be initialized from assignments; for example, they are designated with const. A member which is designated const must be initialized either by the constructor’s argument or through the member initializer list. It cannot be assigned to after initialization. The member initializer list is thus the only place at which a const member can be set to a value.
#include <iostream>
class ImmutablePoint
{
private:
const int x;
const int y;
public:
ImmutablePoint(int xVal, int yVal)
: x{ xVal }
, y{ yVal }
{
}
void print()
{
std::cout << x << ',' << y << '\n';
}
};
int main()
{
ImmutablePoint p{ 7, 11 };
p.print();
return 0;
}
In this case, since x and y are both const, their values can only be provided through the member initializer list. Although they do not need to be explicitly present on the initializer list, because they have not been initialized in the declaration, they are given an indeterminate value. This indeterminate value is then overwritten during the execution of the member initializer list when x and y are actually initialized. If these members had been omitted from the initializer list, the value assigned to them in the member initializer would never have been assigned to them as the assignment would have been skipped.
The statements in the body of a constructor that makes use of a member initializer list are still processed, but only after all members have already been initialized. Such statements would likely need to perform work that does not involve setting the value of one of the member data members of the object being created.
The member initializer list must come when the constructor is defined. If a constructor is declared within the class definition and then defined elsewhere, the member initializer list travels to the definition.
#include <iostream>
class Rectangle
{
private:
double width{};
double height{};
public:
Rectangle(double w, double h);
// Declare area before code needs to call it
double area();
};
Rectangle::Rectangle(double w, double h)
: width{ w }
, height{ h }
{
}
double Rectangle::area()
{
return width * height;
}
int main()
{
Rectangle r{ 3.0, 4.0 };
std::cout << r.area() << '\n';
return 0;
}
The declaration of r’s constructor in the class body simply included the parameters. The actual definition included the member initializer list and left the body of the constructor blank. r’s members were actually initialized from the parameters provided as arguments to its constructor. The subsequent call to area demonstrates this.
Exercise: Constructor Initialization ListsStarter code, hints and solutionOpenClose
Practice: Constructor Initialization Lists
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Create another Rectangle with dimensions 5 and 2 and print its area. Identify where its members are initialized before the constructor body.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
class Rectangle
{
private:
double width{};
double height{};
public:
Rectangle(double w, double h);
// Declare area before code needs to call it
double area();
};
Rectangle::Rectangle(double w, double h)
: width{ w }
, height{ h }
{
}
double Rectangle::area()
{
return width * height;
}
int main()
{
Rectangle r{ 3.0, 4.0 };
std::cout << r.area() << '\n';
return 0;
}Hints
The colon after Rectangle::Rectangle begins the member initializer list. width{w} and height{h} initialize the members before the empty constructor body runs.
Use two different dimensions for another Rectangle and multiply them to predict area(). Member initialization follows declaration order, not a rearranged written initializer order.
Worked solution
A second Rectangle is initialized with width 5 and height 2 through its member initializer list. Its area is 10; no assignment after construction is required.
#include <iostream>
class Rectangle
{
private:
double width{};
double height{};
public:
Rectangle(double w, double h);
// Declare area before code needs to call it
double area();
};
Rectangle::Rectangle(double w, double h)
: width{ w }
, height{ h }
{
}
double Rectangle::area()
{
return width * height;
}
int main()
{
Rectangle r{ 3.0, 4.0 };
std::cout << r.area() << '\n';
Rectangle second{5.0, 2.0};
std::cout << "Second area: " << second.area() << '\n';
return 0;
}
Example output
12
Second area: 10
0 comments
No comments yet. Be the first.