Каковы правила для вызова конструктора суперкласса? - PullRequest
622 голосов
/ 23 сентября 2008

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

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

Ответы [ 9 ]

846 голосов
/ 23 сентября 2008

Конструкторы базовых классов вызываются автоматически, если у них нет аргументов. Если вы хотите вызвать конструктор суперкласса с аргументом, вы должны использовать список инициализации конструктора подкласса. В отличие от 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
        }
};

Подробнее о списке инициализации конструктора здесь и здесь .

218 голосов
/ 23 сентября 2008

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

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

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

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 является телом функции, а не находится внутри тела функции; это позволяет ему отлавливать исключения, возникающие при неявной или явной инициализации члена и базового класса, а также в теле функции. Однако, если блок перехвата функции не выдает другое исключение, среда выполнения перебрасывает исходную ошибку; исключения во время инициализации нельзя игнорировать .

48 голосов
/ 23 сентября 2008

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


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

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


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

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

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

20 голосов
/ 28 января 2014

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

19 голосов
/ 23 сентября 2008

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

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

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

19 голосов
/ 23 сентября 2008

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

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

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

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

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

11 голосов
/ 15 июля 2014

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

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

9 голосов
/ 23 сентября 2008
CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }
6 голосов
/ 12 марта 2014

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...