Constructor Member Initializer Lists

In the previous lesson, we initialized members by assigning values in the constructor body. There's a better way: member initializer lists.

Assignment vs. initialization

Constructor body uses assignment:

class Coordinates
{
public:
    int x{};
    int y{};

    Coordinates(int xPos, int yPos)
    {
        x = xPos;  // Assignment
        y = yPos;  // Assignment
    }
};

What actually happens:

  1. Members are default-initialized (x=0, y=0)
  2. Then assigned new values

This is wasteful—why initialize twice?

Member initializer lists

A member initializer list directly initializes members before the constructor body runs:

class Coordinates
{
public:
    int x{};
    int y{};

    Coordinates(int xPos, int yPos)
        : x{ xPos }, y{ yPos }  // Member initializer list
    {
        // Constructor body (can be empty)
    }
};

Members are initialized once, directly to their final values. More efficient!

Syntax

The member initializer list appears after the constructor parameter list, starting with a colon:

 ClassName(parameters)
    : member1{ value1 }, member2{ value2 }, member3{ value3 }
{
    // Constructor body
}

When member initializer lists are required

Const members must use initializer lists (can't be assigned after creation):

class Setting
{
public:
    const int maxValue{};

    Setting(int max)
        : maxValue{ max }  // Must use initializer list
    {
        // maxValue = max;  // Error: can't assign to const
    }
};

Reference members must use initializer lists:

class Reference
{
public:
    int& target;

    Reference(int& value)
        : target{ value }  // Must use initializer list
    {
        // target = value;  // Error: references can't be reseated
    }
};

Members without default constructors:

class FullName
{
public:
    std::string first{};
    std::string last{};

    FullName(std::string f, std::string l)
        : first{ f }, last{ l }  // Efficient: direct initialization
    {
    }
};

Initialization order

Members are initialized in the order they're declared in the class, NOT the order in the initializer list:

class Order
{
public:
    int first{};
    int second{};

    Order(int value)
        : second{ value }, first{ second * 2 }  // Dangerous!
    {
    }
};

This initializes first first (because it's declared first), using an uninitialized second. Bug!

Warning
List members in initializer list in the same order they're declared in the class to avoid initialization order bugs.

Combining initializer list and constructor body

You can use both:

class Writer
{
public:
    std::string path{};
    std::ofstream output{};

    Writer(std::string filePath)
        : path{ filePath }  // Initialize member
    {
        output.open(path);  // Perform action in body
    }
};

Use the initializer list for member initialization. Use the constructor body for logic, validation, or operations.

Multiple members example

class Area
{
private:
    double width{};
    double height{};
    double size{};

public:
    Area(double w, double h)
        : width{ w }, height{ h }, size{ w * h }
    {
    }

    double getSize() const { return size; }
};

Const correctness

Member initializer lists work well with const members:

class Radius
{
private:
    const double value{};
    const double pi{ 3.14159 };

public:
    Radius(double r)
        : value{ r }
    {
    }

    double circumference() const
    {
        return 2 * pi * value;
    }
};

Once constructed, value and pi cannot change.

Efficiency matters

For class types, member initializer lists avoid unnecessary operations:

class Warrior
{
public:
    std::string title{};

    // Less efficient
    Warrior(std::string t)
    {
        title = t;  // Default construct, then assign
    }

    // More efficient
    Warrior(std::string t)
        : title{ t }  // Direct construction
    {
    }
};

The second version constructs title once, directly with the value. The first constructs it as empty, then assigns.

Best Practice
Prefer member initializer lists over assignment in the constructor body. List members in declaration order to avoid subtle bugs. Use initializer lists for all member initialization, especially const members, reference members, and members without default constructors. Use the constructor body for validation, calculations, setting up resources, and error handling.
class Vault
{
private:
    const int id{};
    int contents{};

public:
    Vault(int vaultId, int initialContents)
        : id{ vaultId }, contents{}  // Initialize in list
    {
        if (initialContents >= 0)  // Validate in body
            contents = initialContents;
    }
};

Summary

Assignment vs. initialization: Constructor bodies use assignment, which means members are first default-initialized and then assigned new values. This is wasteful compared to direct initialization.

Member initializer lists: These directly initialize members before the constructor body runs, eliminating the default initialization step and making construction more efficient.

Required for certain members: Const members, reference members, and members without default constructors must use initializer lists since they cannot be assigned after creation.

Initialization order: Members are initialized in the order they're declared in the class definition, not the order they appear in the initializer list. Always list members in declaration order to avoid bugs.

Combining both approaches: Use initializer lists for member initialization and the constructor body for validation, calculations, or other logic that needs to happen after members are initialized.

Efficiency matters: For class types, initializer lists avoid creating temporary default objects and then assigning to them, instead constructing members directly with their final values.

Member initializer lists are the preferred way to initialize class members. They're more efficient than assignment, required for certain member types, and create clearer, more maintainable code by separating initialization from logic.