调用基类构造函数的规则是什么?

从派生类调用基类构造函数的C++规则是什么?

例如,我知道在Java中,您必须将其作为子类构造函数的第一行(如果不这样做,则假定对no-arg超级构造函数的隐式调用-如果缺少,则会给您一个编译错误)。

979025 次浏览

如果没有参数,基类构造函数会自动为你调用。如果你想用参数调用超类构造函数,你必须使用子类的构造函数初始化列表。与Java不同,C++支持多重继承(无论好坏),所以基类必须按名称引用,而不是“超()”。

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}};

有关构造函数初始化列表这里这里的更多信息。

如果你有一个没有参数的构造函数,它将在派生类构造函数执行之前被调用。

如果你想用参数调用基构造函数,你必须在派生构造函数中显式地写它,如下所示:

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

如果不在C++中调用父类构造函数,就无法构造派生类。如果它是非arg C'tor,则会自动发生这种情况,如果您如上所示直接调用派生构造函数,则会发生这种情况,否则您的代码将无法编译。

将值传递给父构造函数的唯一方法是通过初始化列表。初始化列表是用:实现的,然后是要传递给该类构造函数的类和值的列表。

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

还要记住,如果你有一个构造函数在父类上不接受参数,它将在子构造函数执行之前自动调用。

在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块是函数的主体,而不是在函数的主体中;这允许它捕获由隐式或显式成员和基类初始化抛出的异常,以及在函数主体期间。然而,如果一个函数cat块没有抛出不同的异常,运行时将重新抛出原始错误;初始化不能期间的异常将被忽略。

CDerived::CDerived(): CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0){//construct body}

在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_赋值更好,因为您节省了额外的赋值成本。

请记住,数据成员总是按照它们在类定义中声明的顺序初始化的,无论它们在初始化列表中的顺序如何。为了避免奇怪的错误,如果你的数据成员相互依赖,可能会出现这种错误,你应该始终确保成员在初始化列表和类定义中的顺序是相同的。出于同样的原因,基类构造函数必须是初始化列表中的第一项。如果你完全省略它,那么基类的默认构造函数将被自动调用。在这种情况下,如果基类没有默认构造函数,你将得到编译器错误。

每个人都提到通过初始化列表调用构造函数,但是没有人说父类的构造函数可以从派生成员的构造函数主体显式调用。例如,参见问题从子类的构造函数体调用基类的构造函数。关键是,如果你在派生类的主体中使用对父类或超类构造函数的显式调用,这实际上只是创建父类的实例,而不是在派生对象上调用父类构造函数。在派生类对象上调用父类或超类构造函数的唯一方法是通过初始化列表,而不是在派生类构造函数主体中。所以也许它不应该被称为“超类构造函数调用”。我把这个答案放在这里是因为有人可能会感到困惑(就像我一样)。

当一个类从多个类派生时,没有人提到构造函数调用的顺序。序列是在派生类时提到的。

如果你的基本构造函数中有默认参数,基类将被自动调用。

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个参数的构造函数调用转发到父类std::string

代码

实时版本

#include <iostream>#include <string>
class ChildString: public std::string{public:template<typename... Args>ChildString(Args... args): std::string(args...){std::cout<< "\tConstructor call ChildString(nArgs="<< sizeof...(Args) << "): " << *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(nArgs=1): copy (2)Constructor call ChildString(nArgs=1): from c-string (4)Constructor call ChildString(nArgs=3): substring (3)
Usage:cs1: copy (2)cs2: from c-string (4)cs3: substring (3)

更新:使用可变模板

泛化为n个参数并简化

        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;}

template<typename... Args>ChildString(Args... args): std::string(args...){std::cout<< "\tConstructor call ChildString(nArgs="<< sizeof...(Args) << "): " << *this<< std::endl;}