从派生类调用基类构造函数的 C ++ 规则是什么?
例如,我知道在 Java 中,您必须将其作为子类构造函数的第一行进行(并且如果不这样做,则假定对 no-arg 超级构造函数进行隐式调用 - 如果缺少该函数,则会产生编译错误) 。
如果没有参数,则将自动为您调用基类构造函数。如果要使用参数调用超类构造函数,则必须使用子类的构造函数初始化列表。与 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_的值更可取,因为这样可以节省额外的赋值成本。
请记住,数据成员总是按照它们在类定义中声明的顺序进行初始化,而不管它们在初始化列表中的顺序如何。为避免奇怪的错误(如果您的数据成员相互依赖,可能会出现这些错误),则应始终确保成员顺序在初始化列表和类定义中相同。出于相同的原因,基类构造函数必须是初始化列表中的第一项。如果完全省略,则将自动调用基类的默认构造函数。在这种情况下,如果基类没有默认的构造函数,则会出现编译器错误。