Если конструктор делегирует создание объекта другому конструктору того же класса, какой конструктор отвечае...

Если конструктор делегирует создание объекта другому конструктору того же класса, какой конструктор отвечает за инициализацию членов и что произойдёт при попытке дополнительно указать инициализаторы?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

При делегировании полный объект и все его члены инициализирует целевой конструктор. Делегирующий конструктор может иметь в списке инициализации только вызов другого конструктора этого же класса; одновременно указывать базовые классы или отдельные члены нельзя. После завершения целевого конструктора выполняется тело делегирующего конструктора.

Исторический контекст

Делегирующие конструкторы появились в C++11, чтобы несколько конструкторов могли повторно использовать единую логику инициализации. До этого одинаковые списки инициализации приходилось дублировать либо выносить подготовку данных в отдельные функции, что повышало риск расхождения поведения.

Подход решает именно проблему единственного источника инициализации объекта: один конструктор становится ответственным за корректное создание состояния, а остальные задают удобные варианты входных параметров.

Постановка проблемы

Если разные конструкторы самостоятельно инициализируют одни и те же члены, со временем они могут начать устанавливать разные значения, нарушать инварианты или по-разному обрабатывать ошибки. Делегирование устраняет дублирование, но требует понимать, кто именно создаёт объект и в каком порядке выполняются тела конструкторов.

Неверное ожидание состоит в том, что делегирующий конструктор сначала инициализирует собственные поля, а затем вызывает другой конструктор. В C++ происходит обратное: сначала полностью выполняется целевой конструктор, и только после этого — тело делегирующего.

Подробное решение

Синтаксически делегирующий конструктор указывает в списке инициализации имя текущего класса. Такой вызов должен быть единственным элементом списка инициализации. Поэтому нельзя одновременно делегировать и отдельно задавать значения членов или базовых классов.

#include <string> class User { std::string name; int access; public: User() : User("guest", 0) {} User(std::string n, int a) : name(std::move(n)), access(a) {} };

В примере конструктор без параметров не инициализирует name и access самостоятельно. Это делает целевой конструктор с двумя параметрами. Только после его завершения могло бы выполняться тело конструктора без параметров.

Целевой конструктор инициализирует базовые классы и нестатические члены полного объекта. Они не инициализируются повторно делегирующим конструктором. Если целевой конструктор выбрасывает исключение, тело делегирующего конструктора не выполняется.

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

Делегирование не является вызовом обычной функции после создания объекта. Это часть процесса конструирования, поэтому виртуальные базовые классы, обычные базовые классы и члены создаются только один раз — целевым конструктором.

Ситуация из практики

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

Выбранное решение — сделать один конструктор основным, а остальные направить через делегирование к нему. Тогда проверка диапазонов, установка значений и поддержание инвариантов находятся в одном месте. Результат — меньше дублирования и одинаковое состояние объекта независимо от выбранного способа создания.

Что кандидаты часто упускают

  1. Вопрос: Выполняется ли тело делегирующего конструктора до тела целевого?

    Ответ: Нет. Сначала выполняется целевой конструктор: он инициализирует базовые классы и члены, затем выполняет своё тело. После его успешного завершения выполняется тело делегирующего конструктора. Если целевой конструктор завершился исключением, делегирующее тело не запускается.

  2. Вопрос: Можно ли в делегирующем конструкторе дополнительно инициализировать один из членов класса?

    Ответ: Нельзя. Делегирующий вызов должен быть единственным мем-инициализатором. Попытка добавить рядом инициализацию члена или базового класса делает объявление некорректным. Член нужно инициализировать в целевом конструкторе либо изменить архитектуру конструкторов.

  3. Вопрос: Инициализируются ли виртуальные базовые классы повторно при делегировании?

    Ответ: Нет. Делегирование относится к созданию одного полного объекта, а не к созданию временного промежуточного объекта. Виртуальные базовые классы, обычные базовые классы и члены инициализируются один раз в рамках целевого конструктора, после чего выполняются тела конструкторов в порядке, установленном правилами C++.