What are the rules for calling the superclass constructor?
从一个子类调用超类构造函数的C++规则是什么?
例如,我知道在Java中,你必须把它作为子类构造函数的第一行来做(如果你不这样做,那么就假定对一个没有ARG的超级构造函数进行隐式调用——如果缺少的话,给你一个编译错误)。
如果没有参数,则会自动为您调用基类构造函数。如果要用参数调用超类构造函数,则必须使用子类的构造函数初始化列表。与Java不同,C++支持多继承(更好或更坏),因此基类必须用名称来引用,而不是用"Sub()"来引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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++中,在进入构造函数之前,为所有超类和成员变量调用无参数构造函数。如果要传递这些参数,则有一个称为"构造函数链接"的单独语法,如下所示:
1 2 3 4 5 6 7 8 | class Sub : public Base { Sub(int x, int y) : Base(x), member(y) { } Type member; }; |
如果在这一点上运行了throw,则先前完成构造的基/成员将调用其析构函数,并将异常重新引发给调用方。如果要在链接期间捕获异常,则必须使用函数try块:
1 2 3 4 5 6 7 8 9 10 11 | 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级:
1 2 3 4 5 6 7 | class A : public B { public: A(int a, int b, int c); private: int b_, c_; }; |
那么,假设b有一个取整的构造函数,a的构造函数可能如下所示:
1 2 3 4 5 | A::A(int a, int b, int c) : B(a), b_(b), c_(c) // initialization list { // do something } |
如您所见,在初始化列表中调用基类的构造函数。顺便说一句,初始化初始化列表中的数据成员比在构造函数主体中为b_uuu和c_u分配值要好,因为这样可以节省额外的分配成本。
请记住,数据成员总是按照类定义中声明的顺序进行初始化,而不管它们在初始化列表中的顺序如何。为了避免在数据成员相互依赖的情况下出现奇怪的错误,您应该始终确保在初始化列表和类定义中成员的顺序相同。出于同样的原因,基类构造函数必须是初始化列表中的第一项。如果您完全忽略了它,那么将自动调用基类的默认构造函数。在这种情况下,如果基类没有默认的构造函数,您将得到一个编译器错误。
每个人都提到了通过初始化列表进行的构造函数调用,但没有人说父类的构造函数可以从派生成员的构造函数体显式调用。例如,请参见从子类的构造函数体调用基类的构造函数的问题。要点是,如果在派生类的主体中使用显式调用父类或父类构造函数,则实际上这只是创建父类的实例,而不是在派生对象上调用父类构造函数。在派生类的对象上调用父类或超类构造函数的唯一方法是通过初始化列表,而不是在派生类的构造函数体中。所以它可能不应该被称为"超类构造函数调用"。我把这个答案放在这里是因为有人可能会困惑(就像我一样)。
向父构造函数传递值的唯一方法是通过初始化列表。初始化列表是用一个:实现的,然后是类列表以及要传递给该类构造函数的值。
1 2 3 | Class2::Class2(string id) : Class1(id) { .... } |
还要记住,如果您有一个不接受父类参数的构造函数,它将在子构造函数执行之前自动调用。
如果您有一个没有参数的构造函数,它将在执行派生类构造函数之前被调用。
如果要使用参数调用基构造函数,则必须在派生构造函数中显式写入该参数,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class base { public: base (int arg) { } }; class derived : public base { public: derived () : base (number) { } }; |
不能在C++中调用父构造函数来构造派生类。如果它是一个非参数的构造函数,则会自动发生这种情况;如果您像上面所示直接调用派生的构造函数,则会发生这种情况,否则代码将无法编译。
如果在基本构造函数中有默认参数,则将自动调用基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
1 2 3 4 5 | CDerived::CDerived() : CBase(...), iCount(0) //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0) { //construct body } |
当一个类从多个类派生时,没有人提到构造函数调用的序列。序列在派生类时如前所述。