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

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

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

9 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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
    }

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

Если вы просто хотите передать все аргументы конструктора базовому классу (=parent), вот минимальный пример.

При этом используются шаблоны для перенаправления каждого вызова конструктора с 1, 2 или 3 аргументами в родительский класс. std::string.

Код

#include <iostream>
#include <string>

class ChildString: public std::string
{
    public:
        template <class C>
        ChildString(C arg): std::string(arg)
        {
            std::cout << "\tConstructor call ChildString(C arg): " << *this << std::endl;
        }
        template <class C1, class C2>
        ChildString(C1 arg1, C2 arg2): std::string(arg1, arg2)
        {
            std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
        }
        template <class C1, class C2, class C3>
        ChildString(C1 arg1, C2 arg2, C3 arg3): std::string(arg1, arg2, arg3)
        {
            std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
        }

};

int main()
{
    std::cout << "Check out:" << std::endl;
    std::cout << "\thttp://www.cplusplus.com/reference/string/string/string/" << std::endl;
    std::cout << "for available string constructors" << std::endl;

    std::cout << std::endl;
    std::cout << "Initialization:" << std::endl;
    ChildString cs1 ("copy (2)");

    char char_arr[] = "from c-string (4)";
    ChildString cs2 (char_arr);

    std::string str = "substring (3)";
    ChildString cs3 (str, 0, str.length());

    std::cout << std::endl;
    std::cout << "Usage:" << std::endl;
    std::cout << "\tcs1: " << cs1 << std::endl;
    std::cout << "\tcs2: " << cs2 << std::endl;
    std::cout << "\tcs3: " << cs3 << std::endl;

    return 0;
}

Выход

Check out:
    http://www.cplusplus.com/reference/string/string/string/
for available string constructors

Initialization:
    Constructor call ChildString(C arg): copy (2)
    Constructor call ChildString(C arg): from c-string (4)
    Constructor call ChildString(C1 arg1, C2 arg2, C3 arg3): substring (3)

Usage:
    cs1: copy (2)
    cs2: from c-string (4)
    cs3: substring (3)
Другие вопросы по тегам