Каковы правила вызова конструктора суперкласса?

StackOverflow https://stackoverflow.com/questions/120876

  •  02-07-2019
  •  | 
  •  

Вопрос

Каковы правила C++ для вызова конструктора суперкласса из подкласса?

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

Это было полезно?

Решение

Конструкторы базового класса автоматически вызываются, если у них нет аргументов.Если вы хотите вызвать конструктор суперкласса с аргументом, вы должны использовать список инициализации конструктора подкласса.В отличие от Java, C++ поддерживает множественное наследование (к лучшему или к худшему), поэтому к базовому классу нужно обращаться по имени, а не к «super()».

class SuperClass
{
    public:

        SuperClass(int foo)
        {
            // do something with foo
        }
};

class SubClass : public SuperClass
{
    public:

        SubClass(int foo, int bar)
        : SuperClass(foo)    // Call the superclass constructor in the subclass' initialization list.
        {
            // do something with bar
        }
};

Дополнительная информация о списке инициализации конструктора здесь и здесь.

Другие советы

В C++ конструкторы без аргументов для всех суперклассов и переменных-членов вызываются перед входом в конструктор.Если вы хотите передать им аргументы, для этого существует отдельный синтаксис, называемый «цепочкой конструкторов», который выглядит следующим образом:

class Sub : public Base
{
  Sub(int x, int y)
  : Base(x), member(y)
  {
  }
  Type member;
};

Если что-то запущенное в этот момент выдает ошибку, у баз/членов, которые ранее завершили построение, вызываются их деструкторы, и исключение повторно генерируется вызывающему объекту.Если вы хотите перехватывать исключения во время цепочки, вы должны использовать блок try функции:

class Sub : public Base
{
  Sub(int x, int y)
  try : Base(x), member(y)
  {
    // function body goes here
  } catch(const ExceptionType &e) {
    throw kaboom();
  }
  Type member;
};

Обратите внимание, что в этой форме блок try является тело функции, а не внутри тела функции;это позволяет ему перехватывать исключения, возникающие при неявной или явной инициализации членов и базового класса, а также во время тела функции.Однако если блок catch функции не выдает другое исключение, среда выполнения повторно выдаст исходную ошибку;исключения во время инициализации не могу игнорироваться.

В C++ существует концепция списка инициализации конструктора, в котором вы можете и должны вызывать конструктор базового класса, а также инициализировать элементы данных.Список инициализации идет после подписи конструктора после двоеточия и перед телом конструктора.Допустим, у нас есть класс А:


class A : public B
{
public:
  A(int a, int b, int c);
private:
  int b_, c_;
};

Тогда, предполагая, что у B есть конструктор, принимающий целое число, конструктор A может выглядеть так:


A::A(int a, int b, int c) 
  : B(a), b_(b), c_(c) // initialization list
{
  // do something
}

Как видите, конструктор базового класса вызывается в списке инициализации.Инициализация элементов данных в списке инициализации, кстати, предпочтительнее, чем присвоение значений b_ и c_ внутри тела конструктора, поскольку вы экономите дополнительные затраты на присваивание.

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

Все упоминали вызов конструктора через список инициализации, но никто не говорил, что конструктор родительского класса может быть вызван явно из тела конструктора производного члена.Посмотреть вопрос Вызов конструктора базового класса из тела конструктора подкласса, например.Дело в том, что если вы используете явный вызов родительского класса или конструктора суперкласса в теле производного класса, на самом деле это просто создание экземпляра родительского класса, а не вызов конструктора родительского класса для производного объекта. .Единственный способ вызвать конструктор родительского класса или суперкласса для объекта производного класса — через список инициализации, а не в теле конструктора производного класса.Так что, возможно, это не следует называть «вызовом конструктора суперкласса».Я разместил этот ответ здесь, потому что кто-то может запутаться (как и я).

Единственный способ передать значения родительскому конструктору — через список инициализации.Список инициализации реализуется с помощью:а затем список классов и значений, которые необходимо передать конструктору этих классов.

Class2::Class2(string id) : Class1(id) {
....
}

Также помните, что если у вас есть конструктор, который не принимает параметров родительского класса, он будет вызываться автоматически перед выполнением дочернего конструктора.

Если у вас есть конструктор без аргументов, он будет вызван до выполнения конструктора производного класса.

Если вы хотите вызвать базовый конструктор с аргументами, вам нужно явно написать это в производном конструкторе следующим образом:

class base
{
  public:
  base (int arg)
  {
  }
};

class derived : public base
{
  public:
  derived () : base (number)
  {
  }
};

Вы не можете создать производный класс без вызова родительского конструктора в C++.Это происходит либо автоматически, если это C'tor без аргументов, либо если вы вызываете производный конструктор напрямую, как показано выше, либо ваш код не компилируется.

Если в базовом конструкторе есть параметры по умолчанию, базовый класс будет вызван автоматически.

using namespace std;

class Base
{
    public:
    Base(int a=1) : _a(a) {}

    protected:
    int _a;
};

class Derived : public Base
{
  public:
  Derived() {}

  void printit() { cout << _a << endl; }
};

int main()
{
   Derived d;
   d.printit();
   return 0;
}

Выход:1

CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }

Никто не упомянул последовательность вызовов конструктора, когда класс является производным от нескольких классов.Последовательность такая же, как упоминалось при создании классов.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top