Существуют ли два типа списков инициализаторов элементов в 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.

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


person Roman    schedule 15.08.2013    source источник
comment
Возможно, это дает более четкое представление о том, что происходит, когда два пересекаются.   -  person chris    schedule 15.08.2013


Ответы (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);
  }
};

Может быть много причин, по которым кто-то может захотеть это сделать. В общем, поскольку дочерний класс «больше», чем родительский класс, это естественно только в том случае, если он хочет сделать больше в своем конструкторе, чем просто инициализировать подобъект базового класса.

Несколько слов о времени жизни объекта. Прежде чем войти в тело конструктора, вы создали только подобъекты нового объекта. Сам объект начинает свое время жизни, когда выполнение покидает тело конструктора. Аналогией может быть автомобиль и его части: прежде чем войти в конструктор кузова автомобиля, вы накачиваете шины, собираете двигатель и штампуете части кузова. Но ваша машина не машина, если она не собрана, что происходит в кузове конструктора. Это отражается в деструкторе и особенно заметно при наличии исключений: если во время конструктора объекта возникает исключение, его деструктор не вызывается. Будут вызываться только те деструкторы подобъектов, которые уже полностью сконструированы. Это связано с тем, что если конструктор не завершил выполнение, объект никогда не существовал и не для чего вызывать деструктор.

person Arne Mertz    schedule 15.08.2013
comment
Является ли ошибкой то, что registerSomething не имеет возвращаемого типа (даже недействительного)? - person Roman; 15.08.2013
comment
Да, это была ошибка, исправил. - person Arne Mertz; 15.08.2013
comment
В третьем абзаце: он ищет прямой базовый класс, он ищет прямой базовый класс или виртуальный базовый класс. - person James Kanze; 15.08.2013
comment
@JamesKanze да, спасибо, я всегда забываю о них. А мне они не особо нравятся ;) - person Arne Mertz; 15.08.2013
comment
@ArneMertz Без них вы не сможете написать эффективный C++. (С другой стороны, обычно это интерфейсы без элементов данных и только с конструктором по умолчанию, поэтому их можно игнорировать в списке инициализаторов.) - person James Kanze; 15.08.2013

ParentClass — это тип, а varName1 — это переменная. Это два разных типа сущностей, которые каждый компилятор должен различать.

Есть много случаев, когда вы хотите поместить некоторый код в подкласс ctor. Например, вы хотите вычислить начальное значение члена подкласса на основе правильной инициализации объектов базового класса.

person Eric Z    schedule 15.08.2013
comment
Другими словами, этой строкой ChildClass(string name) : ParentClass( name ) я создаю экземпляр, используя соответствующий конструктор родительского класса. Затем, если у меня есть какой-то контекст между следующими скобками {}, я изменяю созданный объект, а затем возвращаю его. Это правильно? - person Roman; 15.08.2013
comment
Строго говоря, : ParentClass(name) не создает объект, а инициализирует базовую часть объекта childClass. Память уже выделена, конструктор просто должен правильно инициализировать эту память. Да, вы можете изменить базовый объект/часть в теле подкласса ctor. Но вы должны знать, что ctor ничего не возвращает. - person Eric Z; 15.08.2013
comment
@Roman Я добавил в свой ответ несколько слов о времени жизни объектов. Тело конструктора ChildClass может изменять подобъект ParentClass, но, строго говоря, объект ChildClass не изменяется, поскольку он еще не существует. - person Arne Mertz; 15.08.2013

Они действительно одинаковы для компилятора: список инициализаторов используется для инициализации подобъектов. Если подобъект является базовым классом, он именуется по своему типу; если это член, он называется по имени члена; но принцип один и тот же в обоих случаях.

Применяются обычные правила поиска имени. Если вы дадите члену то же имя, что и базовый класс, он скроет базовый класс, и вы не сможете указать базовый класс в списке инициализаторов (это означает, что базовый класс должен иметь конструктор по умолчанию). (Не делайте этого. Установите соглашение об именах, чтобы имена типов и имена переменных никогда не конфликтовали.)

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

person James Kanze    schedule 15.08.2013
comment
Вы можете указать базовый класс, полностью уточнив его, поэтому не требуется ctor по умолчанию (см. Мой ответ). Строго говоря, вы не можете выполнять какую-либо предварительную обработку в теле конструктора до инициализации члена, только перед присвоением окончательных значений членам. При входе в тело конструктора каждый член уже инициализирован. - person Arne Mertz; 15.08.2013
comment
@ArneMertz Да, вы можете указать базу, полностью указав ее. Члены POD не инициализируются, если они не указаны в списке инициализаторов. Вы можете выполнить довольно много предварительной обработки в теле конструктора, если члены имеют тип POD. (Строго говоря, конечно, вы присваиваете, а не инициализируете, но с типами POD особой разницы нет. Объект не был инициализирован до, а он после.) - person James Kanze; 15.08.2013
comment
да, противоречивый характер §8.5,6: Инициализация по умолчанию [POD] означает, что инициализация не выполняется, поэтому инициализируется ли POD, инициализированный по умолчанию, или нет? ... - person Arne Mertz; 15.08.2013
comment
@АрнеМерц Да. Инициализация по умолчанию не является инициализацией, но присваивание не является инициализацией, поэтому... (На самом деле я использовал инициализацию в более общем смысле, а не в строгом смысле стандарта. Так что мои комментарии о предварительной обработке можно было бы применить даже к std::string: технически вы инициализируете его пустым, а затем присваиваете ему значение, но концептуально начальное значение — это то, которое вы вычисляете в теле конструктора). - person James Kanze; 15.08.2013

Что мне непонятно, так это то, как компилятор различает эти два случая (синтаксис тот же). Например, во втором примере компилятор может подумать, что ему нужно взять значение переменной name и присвоить его переменной ParentClass класса ChildClass, а затем он увидит, что такая переменная не объявлена ​​в классе ChildClass.

Компилятор знает, что ParentClass — это тип, не являющийся членом ChildClass. Вы не сможете объявить участника с именем ParentClass

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

Это для случаев, когда вы хотите использовать какой-то конкретный конструктор не по умолчанию в ParentClass.

person Karadur    schedule 15.08.2013
comment
Вы будете объявлять участника с именем ParentClass. - person Arne Mertz; 15.08.2013
comment
This is for the cases where you want to use some specific non-default constructor in ParentClass. Но даже без содержимого между {} я уже указал, какой конструктор родительского класса я хочу использовать (по умолчанию или нет). - person Roman; 15.08.2013
comment
В теле конструктора вы обычно делаете некоторые вещи, которые нельзя сделать с помощью простой инициализации/назначения членам класса. - person Karadur; 15.08.2013