Существуют ли два типа списков инициализаторов членов в C++?
Я видел два разных способа использования списков инициализаторов членов. Первый такой:
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
,
Второй неясный момент для меня заключается в том, почему мы можем захотеть поместить некоторый контент в тело конструктора из второго примера. Даже если нет ничего, что он уже создает и возвращает объект, используя конструктор родительского класса.
4 ответа
Что мне не понятно, так это то, как компилятор различает эти два случая (синтаксис один и тот же).
Увидев инициализатор в списке, компилятор сначала ищет переменные-члены с этим именем. Если он находит его, он пытается инициализировать этот член с заданными аргументами. Если это не так, он ищет прямой базовый класс или виртуальный базовый класс с этим именем, чтобы инициализировать подобъект базового класса с заданными аргументами. Это обычные правила поиска имен, применяемые, когда вы называете что-то внутри метода класса (включая конструкторы).
В редком и плохо спроектированном случае, когда у вас есть и переменная-член, и прямой базовый класс с одним и тем же именем, вам нужно будет квалифицировать тип базового класса, то есть добавить пространство имен:
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
,