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:
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;
}
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
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.
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
Run the program: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:
It should be changed to:
Animal(int age, std::string name) : age_(age), name_(name) {
std::cout << "Animal(int age) is called" << std::endl;
}
Related Topics¶
- C++ Avoiding the Default Constructor | C++那些事
- C++ Prefer Class Templates over Function Templates | C++那些事
- C++ copy-and-swap Idiom | C++那些事
- C++ Dependency Injection | C++那些事
- C++ enum class Strongly Typed Enums | C++那些事
- C++ Header-Only vs Implementation Separation | C++那些事
- C++ Lazy Evaluation Idiom | C++那些事
- C++ Lookup Table Idiom | C++那些事
- C++ Member Pointers and Type Information | C++那些事
- C++ Mixin Idiom | C++那些事