Я видел два разных способа использования списков инициализаторов членов. Первый такой:
class ClassName {
public:
arg_type_1 varName1;
arg_type_2 varName2;
// Constructor.
ClassName(arg_type_1 arg_name_1, arg_type_2 arg_name_2)
: varName1(arg_name_1), varName2(arg_name_2)
{
}
}
Что там происходит, понятно. В конструкторе у нас есть список аргументов, и мы используем их для инициализации членов класса. Например arg_name_1
используется для инициализации значения для varName1
переменная класса.
Другой способ использования инициализатора члена появляется в случае наследования:
class ChildClass : public ParentClass
{
ChildClass(string name) : ParentClass( name )
{
// What can we put here and why we might need it.
}
};
Что здесь происходит, тоже понятно. Когда мы вызываем конструктор ChildClass
с одним строковым аргументом, он вызывает конструктор ParentClass
с тем же строковым аргументом.
Что мне не понятно, так это то, как компилятор различает эти два случая (синтаксис один и тот же). Например, во втором примере компилятор может подумать, что ему нужно принять значение name
переменная и назначить его ParentClass
переменная ChildClass
а затем он видит, что такая переменная не объявлена в ChildClass
,
Второй неясный момент для меня заключается в том, почему мы можем захотеть поместить некоторый контент в тело конструктора из второго примера. Даже если он уже ничего не создает и не возвращает объект, используя конструктор родительского класса.
Что мне не понятно, так это то, как компилятор различает эти два случая (синтаксис один и тот же).
Увидев инициализатор в списке, компилятор сначала ищет переменные-члены с этим именем. Если он находит его, он пытается инициализировать этот член с заданными аргументами. Если это не так, он ищет прямой базовый класс или виртуальный базовый класс с этим именем, чтобы инициализировать подобъект базового класса с заданными аргументами. Это обычные правила поиска имен, применяемые, когда вы называете что-то внутри метода класса (включая конструкторы).
В редком и плохо спроектированном случае, когда у вас есть и переменная-член, и прямой базовый класс с одним и тем же именем, вам нужно будет квалифицировать тип базового класса, то есть добавить пространство имен:
struct B1 {
B1(char const*) {};
};
namespace Foo {
struct B2 {
B2(bool) {};
};
}
struct Weird : public B1, public Foo::B2 {
int B1;
double B2;
Weird()
: ::B1("meow")
, Foo::B2(false)
, B1(42)
, B2(3.14)
{}
};
Второй неясный момент для меня заключается в том, почему мы можем захотеть поместить некоторый контент в тело конструктора из второго примера. Даже если он уже ничего не создает и не возвращает объект, используя конструктор родительского класса.
На самом деле он не возвращает никакого объекта, он просто создает этот объект. Однако кто-то может захотеть вызвать дополнительные функции, например:
class ParentClass {
public:
ParentClass(string name);
void registerSomething(ParentClass const&);
}
class ChildClass : public ParentClass {
public:
ChildClass(string name) : ParentClass( name ) {
registerSomething(*this);
}
};
Там может быть много причин, почему кто-то может захотеть сделать это. В общем, поскольку дочерний класс «больше», чем родительский класс, это естественно, если он хочет сделать больше в своем конструкторе, чем просто инициализировать подобъект базового класса.
Несколько слов о времени жизни объекта:
Перед входом в тело конструктора вы только сконструировали подобъекты нового объекта. Сам объект начинает свое время жизни, когда выполнение покидает тело конструктора. Аналогия может быть с автомобилем и его частями: перед тем, как приступить к конструированию кузова автомобиля, вы накачали шины, собрали двигатель и штамповали части кузова. Но ваш автомобиль не является автомобилем, если он не был собран, что происходит в теле конструктора. Это отражается в деструкторе и особенно может быть замечено при наличии исключений: если исключение происходит во время конструктора объекта, его деструктор не будет вызван. Будут вызваны только деструкторы подобъектов, которые уже построены полностью. Это происходит потому, что если конструктор не завершил выполнение, объект никогда не существовал и нечего вызывать деструктор.
ParentClass
это тип и varName1
переменная Это два разных типа сущностей, которые должен различать каждый компилятор.
Есть много случаев, когда вы хотите поместить некоторый код в ctor подкласса. Например, вы хотите вычислить начальное значение члена подкласса на основе правильной инициализации объектов базового класса.
Это действительно то же самое для компилятора: список инициализатора
используется для инициализации подобъектов. Если подобъект является базой
класс, он назван по типу; если это член, он называется
по имени участника; но принцип одинаков в обоих случаях.
Применяются обычные правила поиска имен. Если вы даете члену
то же имя, что и базовый класс, он будет скрывать базовый класс, и вы
не может указать базовый класс в списке инициализатора (который
означает, что базовый класс должен иметь конструктор по умолчанию).
(Не делай этого. Установите соглашение об именах, чтобы этот тип
имена и имена переменных никогда не могут конфликтовать.)
Относительно того, почему вы могли бы хотеть код в фактическом теле
конструктор, может быть много причин. Чаще всего это будет
из-за какой-то постобработки, что вам делать на
инициализированные члены. В других случаях это может быть потому, что вы
необходимо выполнить некоторую предварительную обработку, прежде чем вы сможете инициализировать
члены. Или вы можете реорганизовать некоторые общие обработки
в отдельную функцию.
Что мне не понятно, так это то, как компилятор различает эти два случая (синтаксис один и тот же). Например, во втором примере компилятор может подумать, что ему нужно принять значение переменной name и присвоить его переменной ParentClass класса ChildClass, а затем он увидит, что такая переменная не объявлена в ChildClass.
Компилятор знает, что ParentClass
тип не является членом ChildClass
, Вы не сможете объявить участника с именем ParentClass
Второй неясный момент для меня заключается в том, почему мы можем захотеть поместить некоторый контент в тело конструктора из второго примера. Даже если он уже ничего не создает и не возвращает объект, используя конструктор родительского класса.
Это для случаев, когда вы хотите использовать какой-то конкретный конструктор не по умолчанию в ParentClass
,