Каковы правила вызова конструктора базового класса?
Каковы правила C++ для вызова конструктора базового класса из производного класса?
Например, я знаю, что в Java это необходимо делать в первой строке конструктора подкласса (и если вы этого не сделаете, то предполагается неявный вызов конструктора базового класса без аргументов, что приведет к ошибке компиляции, если такой конструктор отсутствует).
5 ответ(ов)
В 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)
{
// здесь идет тело функции
} 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) // список инициализации
{
// выполнение каких-либо операций
}
Как видно, конструктор базового класса вызывается в списке инициализации. Инициализация членов данных в этом списке предпочтительнее, чем присваивание значений для b_ и c_ в теле конструктора, так как это позволяет избежать дополнительных затрат на присваивание.
Имейте в виду, что члены данных всегда инициализируются в том порядке, в котором они объявлены в определении класса, независимо от их порядка в списке инициализации. Чтобы избежать странных ошибок, которые могут возникнуть, если ваши члены данных зависят друг от друга, всегда следует следить, чтобы порядок членов совпадал в списке инициализации и в определении класса. По той же причине конструктор базового класса должен быть первым элементом в списке инициализации. Если вы совсем его пропустите, то будет автоматически вызван конструктор по умолчанию для базового класса. В этом случае, если у базового класса нет конструктора по умолчанию, вы получите ошибку компиляции.
Если у вас есть конструктор без аргументов, он будет вызван до того, как выполнится конструктор производного класса.
Если вы хотите вызвать базовый конструктор с аргументами, вам нужно явно указать это в конструкторе производного класса, например так:
class base
{
public:
base(int arg)
{
}
};
class derived : public base
{
public:
derived() : base(number)
{
}
};
В C++ нельзя создать объект производного класса без вызова конструктора родительского класса. Это происходит автоматически, если конструктор базового класса не имеет аргументов, либо если вы явно вызываете его из конструктора производного класса, как показано выше, в противном случае ваш код не скомпилируется.
Единственный способ передать значения в конструктор родительского класса — это использовать список инициализации. Список инициализации задается двоеточием (:) и включает в себя список классов и значения, которые будут переданы в конструкторы этих классов.
Class2::Class2(string id) : Class1(id) {
....
}
Также имейте в виду, что если в родительском классе есть конструктор без параметров, он будет вызван автоматически перед выполнением конструктора дочернего класса.
Если в вашем базовом конструкторе заданы значения по умолчанию, то базовый класс будет вызываться автоматически. В приведённом примере, класс Base
имеет конструктор, принимающий один параметр a
, который по умолчанию равен 1:
class Base
{
public:
Base(int a=1) : _a(a) {}
protected:
int _a;
};
Класс Derived
наследует от Base
, но не определяет явный конструктор. При этом, компилятор автоматически вызывает конструктор базового класса Base
с параметром по умолчанию при создании объекта Derived
:
class Derived : public Base
{
public:
Derived() {}
void printit() { cout << _a << endl; }
};
В функции main
создается объект d
класса Derived
, а затем вызывается метод printit
, который выводит значение _a
:
int main()
{
Derived d;
d.printit();
return 0;
}
Так как конструктор Base
был вызван с использованием значения по умолчанию (1), программа выводит:
1
Таким образом, даже если вы не явным образом вызываете конструктор базового класса в вашем производном классе, компилятор сделает это за вас, используя параметры по умолчанию, если они указаны.
Какова разница между публичным, приватным и защищённым наследованием?
Имеют ли круглые скобки после имени типа значение при использовании new?
Какова разница между 'typedef' и 'using'?
Является ли List<Собака> подклассом List<Животное>? Почему дженерики в Java не являются неявно полиморфными?
Что означает 'const' в конце объявления метода класса?