Skip to content

Last updated:2026-10-12

C++ Class Initializer List Idiom

  • const members can only be initialized in the constructor's initializer list
  • reference members likewise can only be initialized in the constructor's initializer list
  • object members (whose corresponding class has no default constructor) can also only be initialized in the constructor's initializer list

Nesting Between Classes

First approach: use an initializer list.

class Animal {
public:
    Animal() {
        std::cout << "Animal() is called" << std::endl;
    }

    Animal(const Animal &) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal &) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog {
public:
    Dog(const Animal &animal) : __animal(animal) {
        std::cout << "Dog(const Animal &animal) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

private:
    Animal __animal;
};

int main() {
    Animal animal;
    std::cout << std::endl;
    Dog d(animal);
    std::cout << std::endl;
    return 0;
}

Output:

Animal() is called

Animal (const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

Analyzing from top to bottom:

In the main function, Animal animal; invokes the default constructor.

Dog d(animal); is equivalent to:

Animal __animal = animal;
which actually calls the copy constructor, hence the output:
Animal (const Animal &) is called
Then the output inside Dog's constructor is printed.

Finally the destructors are called and the program ends.

Second approach: initialize the object by assignment in the constructor.

Change the constructor as follows:

Dog(const Animal &animal) {
    __animal = animal;
    std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
The output is now:
Animal() is called

Animal() is called
Animal & operator=(const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called
From this we conclude:

When Dog d(animal); is called, it is equivalent to:

defining the object first and then assigning to it, so the default constructor is called first, followed by the = operator overload function.

// 假设之前已经有了animal对象
Animal __animal;
__animal = animal;

Summary

From the above we draw the following conclusions:

  • If a class contains other custom classes or structs, using the initializer list actually creates the object and initializes it at the same time
  • Whereas assigning inside the class is equivalent to defining the object first and then assigning to it; generally the default constructor is called first, then the = operator overload function.

In an Inheritance Relationship Without a Default Constructor

Now consider changing the relationship above to inheritance, and modify the constructors of Animal and Dog, as in the following code:

class Animal {
public:
    Animal(int age) {
        std::cout << "Animal(int age) is called" << std::endl;
    }

    Animal(const Animal & animal) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal & amimal) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog : Animal {
public:
    Dog(int age) : Animal(age) {
        std::cout << "Dog(int age) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

};

The code above passes arguments to the base class's parameterized constructor via the initializer list. What happens if we do not pass them through the initializer list?

Remove the initializer list

Dog(int age)  {
    std::cout << "Dog(int age) is called" << std::endl;
}
Run the program:

error: no matching function for call to ‘Animal::Animal()’

Because Animal has no default constructor, an error is reported. This kind of problem is catastrophic, and we should avoid it; we can initialize the base class's constructor via the initializer list.

const and Reference Data Members in a Class

Reference data members in particular must be initialized with the initializer list, not by assignment!

For example: add private members to the Animal above and change the constructor:

class Animal {
public:
    Animal(int age,std::string name) {
        std::cout << "Animal(int age) is called" << std::endl;
    }
private:
    int &age_;
    const std::string name_;
};

The following error is reported:

error: uninitialized reference member in ‘int&’

It should be changed to:

Animal(int age, std::string name) : age_(age), name_(name) {
    std::cout << "Animal(int age) is called" << std::endl;
}

Comments