В производном классе используют конструктор базового класса через using. Определите, как будут инициализированы базовая часть и собственное поле объекта:
#include <cassert>
struct Base {
explicit Base(int value) : value(value) {}
int value;
};
struct Derived : Base {
using Base::Base;
int status = 42;
};
int main() {
Derived object(7);
assert(object.value == 7);
assert(object.status == 42);
}
Объясните механизм выбора и выполнения унаследованного конструктора.
Вызов Derived object(7) использует конструктор Base(int), доступный в Derived через using Base::Base. Он инициализирует базовую часть объекта значением 7, после чего поле Derived::status инициализируется своим default member initializer значением 42.
Унаследованный конструктор не становится обычным конструктором, написанным вручную в производном классе. Его задача — построить базовый подобъект; остальные подобъекты производного класса инициализируются по обычным правилам.
До C++11 производному классу обычно приходилось вручную объявлять конструкторы, которые передавали аргументы конструкторам базового класса. Это создавало шаблонный код и требовало поддерживать несколько почти одинаковых перегружающих конструкторов.
Механизм унаследованных конструкторов позволяет явно сделать конструкторы базового класса кандидатами при создании производного объекта. При этом сохраняется корректная инициализация всей иерархии, включая члены производного класса.
Производный класс может содержать собственные поля, базовые подобъекты и виртуальные базовые подобъекты. Нельзя считать, что вызов конструктора базового класса автоматически инициализирует весь производный объект.
Если поле производного класса имеет default member initializer, он будет использован. Если такого инициализатора нет, поле должно быть корректно default-initializable; иначе вызов унаследованного конструктора завершится ошибкой компиляции.
При Derived object(7) компилятор рассматривает унаследованный Base(int) как подходящий конструктор для создания Derived. Сначала вызывается Base(int), который строит базовый подобъект и записывает 7 в Base::value.
Затем инициализируются члены Derived в порядке их объявления. Для status применяется выражение = 42, поэтому после завершения конструирования выполняется условие object.status == 42.
Важное ограничение: если член нельзя инициализировать без аргументов, одного using Base::Base недостаточно:
В этом примере Derived d(1) корректен, потому что std::string имеет конструктор по умолчанию. Но член пользовательского типа без доступного конструктора по умолчанию потребовал бы отдельного конструктора Derived с явной инициализацией.
Унаследованный конструктор не позволяет передать аргументы непосредственно членам производного класса. Например, Derived(7, 42) не появится автоматически только потому, что у Derived есть поле status. Для такой сигнатуры нужно объявить собственный конструктор.
Если в производном классе есть собственный конструктор с той же сигнатурой, он разрешает конфликт и используется вместо унаследованного. Кроме того, правила доступа к исходному конструктору базового класса сохраняются: недоступный конструктор нельзя сделать доступным простым объявлением using.
В библиотеке есть базовый класс конфигурации:
Вариант с ручным конструктором Client(int timeout) : Config(timeout) {} явно контролирует интерфейс, но дублирует код и требует изменений при добавлении новых конструкторов Config. Вариант с using Config::Config сокращает код и автоматически отражает конструкторы базового класса, но может открыть в производном типе больше способов создания, чем желательно для его публичного API.
Здесь выбран using, потому что Client не добавляет обязательных параметров, а logging имеет безопасное значение по умолчанию. В результате Client c(1000) корректно получает timeout == 1000 и logging == false. Если бы производному классу требовалась обязательная проверка или дополнительная логика создания, лучше объявить собственные конструкторы и явно контролировать интерфейс.
Ответ: Нет, автоматически — нельзя. Аргументы унаследованного конструктора предназначены для выбранного конструктора базового класса. Члены производного класса инициализируются default member initializer либо правилами default-initialization. Чтобы передать отдельный аргумент полю производного класса, требуется собственный конструктор производного класса.
Ответ: Собственный конструктор производного класса становится выбранным кандидатом для этой сигнатуры. Он сам отвечает за инициализацию базового подобъекта и членов производного класса через список инициализации или default member initializer. Объявление собственного конструктора позволяет добавить проверки и изменить поведение, но одновременно увеличивает объём кода.
Ответ: Вызов унаследованного конструктора не скомпилируется. После построения базовой части компилятор должен построить и каждый член производного класса, а для такого поля нет подходящего способа инициализации. Нужно добавить default member initializer с необходимыми аргументами либо объявить собственный конструктор производного класса, который явно инициализирует это поле.