CS 70

Member Initialization Lists

  • Duck speaking

    What's up with the colon in the definition of the constructor?

  • LHS Cow speaking

    Oh, yeah! When we define constructors in the source file (.cpp) we use some syntax that is probably unfamiliar to you.

  • Pig speaking

    MORE syntax?!

  • LHS Cow speaking

    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.
  • LHS Cow speaking

    This code is different than in Java and Python where your constructor would have a bunch of assignment statements!

  • Horse speaking

    Whoah, whoah! Doesn't that mean that the constructor might have nothing to do?

  • LHS Cow speaking

    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.

  • RHS Cow speaking

    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.

  • LHS Cow speaking

    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:

  1. noseTwitchPerSecond_ has the value of the parameter ntps (rather than the 0 value specified in the member-variable declaration).
  2. floppiness_ has two times the value of the parameter earLength (rather than the 0.0 value specified in the member-variable declaration).
  3. feedingHistory_ has true for the first timesFed entries and false for the rest (rather than the indeterminate values specified in the member-variable declaration).

Give the definition of the Bunny constructor (using the member-initialization list where appropriate).

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.)