Перейти к содержанию

Последнее обновление:2026-10-12

Идиома списков инициализации класса в C++

  • инициализация const-членов возможна только в списке инициализации конструктора
  • инициализация членов-ссылок — тоже только в списке инициализации конструктора
  • инициализация объектных членов (когда класс соответствующего объектного члена не имеет конструктора по умолчанию) — тоже только в списке инициализации конструктора

Вложенность классов

Первый способ: использовать список инициализации.

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;
}

Результат работы:

Animal() is called

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

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

Проанализируем по порядку сверху вниз:

В функции main строка Animal animal; вызывает конструктор по умолчанию.

Dog d(animal); эквивалентно:

Animal __animal = animal;
Фактически вызывается копирующий конструктор, поэтому выводится:
Animal (const Animal &) is called
Затем печатается вывод из конструктора Dog.

В конце вызываются деструкторы, и программа завершается.

Второй способ: инициализация объекта присваиванием в конструкторе.

Изменим конструктор следующим образом:

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

Отсюда вывод:

Вызов Dog d(animal); эквивалентен:

сначала создаётся объект, а затем выполняется присваивание, поэтому сначала вызывается конструктор по умолчанию, а затем перегруженный оператор =.

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

Краткие итоги

Из рассмотренного выше мы делаем следующие выводы:

  • если класс содержит другие пользовательские class или struct, то при использовании списка инициализации объект фактически создаётся и инициализируется одновременно
  • а при использовании присваивания внутри класса это эквивалентно сначала определению объекта, а затем присваиванию: обычно сначала вызывается конструктор по умолчанию, а затем перегруженный оператор =.

Наследование при отсутствии конструктора по умолчанию

Теперь рассмотрим замену описанных выше отношений на наследование и изменим конструкторы Animal и Dog, как в коде ниже:

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;
    }

};

Выше параметры передаются параметризованному конструктору базового класса через список инициализации. А что произойдёт, если не передавать их через список инициализации?

Уберём список инициализации

Dog(int age)  {
    std::cout << "Dog(int age) is called" << std::endl;
}
Запустим программу:

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

Поскольку в Animal нет конструктора по умолчанию, возникает ошибка — такая проблема катастрофична, и её следует по возможности избегать, инициализируя конструктор базового класса через список инициализации.

const-члены и члены-ссылки в классе

Особенно члены-ссылки: их необходимо инициализировать в списке инициализации, а не присваиванием!

Например: добавим в рассмотренный выше Animal закрытые члены и изменим конструктор:

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_;
};

Получим следующую ошибку:

error: uninitialized reference member in ‘int&’

Следует изменить так:

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

Похожие темы

Комментарии