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

Другие вопросы по тегам