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

519

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

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

  • 6
    Просто придирки: в C ++ нет «суперкласса», на самом деле, стандарт вообще не упоминает об этом. Эта формулировка происходит от Java (скорее всего). Используйте «базовый класс» в C ++. Я предполагаю, что super подразумевает одного родителя, в то время как C ++ допускает множественное наследование.
Теги:
inheritance
constructor

9 ответов

692
Лучший ответ

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

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

  • 46
    Я удалил «явный» из конструктора SuperClass. Несмотря на то, что это лучшая практика для конструкторов с одним аргументом, обсуждение не было уместным. Для получения дополнительной информации о явном ключевом слове см .: weblogs.asp.net/kennykerr/archive/2004/08/31/…
  • 1
    Оператор двоеточия:, который вы использовали для вызова конструктора суперкласса перед созданием экземпляра конструктора дочернего класса, я полагаю, это также верно для методов?
Показать ещё 7 комментариев
162

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

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

  • 1
    Я не уверен, что понимаю синтаксис вашего второго примера ... Является ли конструкция try / catch заменой тела конструктора?
  • 2
    Да. Я переписал раздел и исправил ошибку (ключевое слово try идет перед списком инициализации). Я должен был искать это вместо того, чтобы писать по памяти, это не то, что часто используется :-)
Показать ещё 3 комментария
38

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


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

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


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

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

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

  • 1
    Подождите секунду ... Вы говорите, инициализаторы экономят на стоимости назначений. Но разве в них не выполняются те же самые задания, если их вызывают?
  • 5
    Нету. Инициирование и назначение - разные вещи. Когда вызывается конструктор, он пытается инициализировать каждый элемент данных тем значением, которое он считает значением по умолчанию. В списке инициализации вы можете указать значения по умолчанию. Таким образом, вы берете на себя стоимость инициализации в любом случае.
Показать ещё 3 комментария
16

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

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

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

14

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

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

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

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

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

10

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

  • 10
    Этот ответ несколько сбивает с толку, хотя я несколько раз перечитал его и посмотрел на связанный вопрос. Я думаю, что это говорит о том, что если вы используете явный вызов родительского класса или конструктора суперкласса в теле производного класса, это на самом деле просто создает экземпляр родительского класса, а не вызывает родительский класс. конструктор на производном объекте. Единственный способ вызвать родительский класс или конструктор суперкласса для объекта производного класса - через список инициализации, а не в теле конструктора производного класса.
  • 0
    @Richard Chambers Это может сбивать с толку, поскольку английский не мой родной язык, но вы точно описали то, что я пытался сказать.
7

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

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

  • 0
    Это просто потому, что это конкретное объявление создает неявный Base() , который имеет то же тело, что и Base(int) но плюс неявный инициализатор для : _a{1} . Это Base() который всегда вызывается, если в init-list нет определенного базового конструктора. И, как уже упоминалось в другом месте, делегирующие конструкторы C ++ 11 и инициализация в скобках или равнозначные аргументы делают аргументы по умолчанию менее необходимыми (когда они уже были похожи на запах кода во многих примерах).
6
CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }
1

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

  • 2
    Если никто не говорил об этом, где это было упомянуто?
  • 3
    @EJP, так как вопрос касается правил вызова, стоит упомянуть последовательность вызова в ответе

Ещё вопросы

Сообщество Overcoder
Наверх
Меню