Member Initialization Lists
What's up with the colon in the definition of the constructor?
Oh, yeah! When we define constructors in the source file (
.cpp
) we use some syntax that is probably unfamiliar to you.MORE syntax?!
Check out this snippet from
cow.cpp
.
Cow::Cow(int numSpots, int age)
: numSpots_{numSpots},
age_{age}
{
cout << "Made a cow with " << numSpots_ << " spots!" << endl;
}
numSpots_
and age_
are member variables of the Cow
class.
Here are some things we know:
- When we initialize an object, all of its member variables need to be initialized.
- We can refer to member variables inside the body of the constructor. In this example we are printing out the value of a member variable.
- You can't use a variable until it is initialized.
Therefore… these variables must be initialized before we enter the body of the constructor!
Initializing Members with the Member-Initialization List
The highlighted stuff starting with the colon (:
) and before the opening curly brace of the function body is called the member-initialization list. The member-initialization list is the moment when all of the member variables get initialized.
If it helps, you can imagine that all of the member variables are being declared at the top of your constructor. If that were literally true, our Cow
constructor would look like
Cow::Cow(int numSpots, int age) { // This code wouldn't actually work!!!
int numSpots_{numSpots};
int age_{age};
cout << "Made a cow with " << numSpots_ << " spots!" << endl;
}
This code probably looks more familiar—it certainly initializes these variables. But it doesn't work the way we want, because numSpots_
and age_
are being declared as local variables here. They would be destroyed and deallocated at the ending curly brace.
Instead, members are initialized after the parameters are initialized but before the body of the function:
Cow::Cow(int numSpots, int age)
: numSpots_{numSpots}, // This code works!
age_{age}
{
cout << "Made a cow with " << numSpots_ << " spots!" << endl;
}
C++ doesn't care about whitespace, so we could also write the code as
Cow::Cow(int numSpots, int age) : numSpots_{numSpots}, age_{age} {
// ^--- This code also works!
cout << "Made a cow with " << numSpots_ << " spots!" << endl;
}
Notes
Ordering rule: Member variables are initialized in the order they are declared, so the member-initialization list should follow the same order that the member variables are declared in the class definition. Otherwise you'll get a warning that your code is misleading because it suggests a different order than will actually occur.
Missing variables: If a member does not appear in the initialization list, it will be initialized as specified in the class definition. (If you haven't specified any initialization, any primitive type will have an indeterminate value, and an object of class type, will invoke its default constructor.)
CS 70 Coding Conventions
- Use the initialization list whenever you can—any member that can be initialized this way should be in the member-initialization list
- Do not use an assignment statement in the constructor body when you can use the member-initialization list.
- In the class definition, when declaring member variables, provide a safe initial value (e.g., zero) for all primitive types, even if you're going to override that value with a member-initialization list in your constructor(s). It won't do any harm, and it'll catch some tricky bugs you can get with indeterminate values if you forget to put the member in the member-initialization list.
This code is different than in Java and Python where your constructor would have a bunch of assignment statements!
Whoah, whoah! Doesn't that mean that the constructor might have nothing to do?
Totally! In C++ it's pretty common for a constructor to have an empty body when all the work can be done in the initialization list.
Even in our
cow.cpp
example, the constructor just has a print statement that doesn't really do anything. If we didn't want that output to the screen, the function body would be empty.That said, sometimes constructors have to do some complicated procedures to initialize the object. All of that logic would go in the body of the function.
An Example
Say we have the following class:
class Bunny {
public:
Bunny(float ntps, float earLength, size_t timesFed);
private:
float noseTwitchPerSecond_ = 0;
float floppiness_ = 0.0;
bool feedingHistory_[3]; // CAUTION: Will be default initialized!!
};
When a Bunny
is created, we want the following to be true:
noseTwitchPerSecond_
has the value of the parameterntps
(rather than the0
value specified in the member-variable declaration).floppiness_
has two times the value of the parameterearLength
(rather than the0.0
value specified in the member-variable declaration).feedingHistory_
hastrue
for the firsttimesFed
entries andfalse
for the rest (rather than the indeterminate values specified in the member-variable declaration).
One implementation looks like
Bunny::Bunny(float ntps, float earLength, size_t timesFed)
: noseTwichPerSecond_{ntps},
floppiness_{2 * earLength}
{
for (size_t i = 0; i < timesFed; ++i) {
feedingHistory_[i] = true;
}
for (size_t i = timesFed; i < 3; ++i) {
feedingHistory_[i] = false;
}
}
(This example is not the only way to accomplish this task, so your implementation may vary.)
The video below will walk you through the initialization of a Bunny
object.
(When logged in, completion status appears here.)