780

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

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

4

10 回答 10

1063

如果它们没有参数,则会自动为您调用基类构造函数。如果要调用带参数的超类构造函数,则必须使用子类的构造函数初始化列表。与 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
        }
};

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

于 2008-09-23T13:18:34.853 回答
265

在 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 块没有抛出不同的异常,则运行时将重新抛出原始错误;初始化期间的异常不容忽视。

于 2008-09-23T13:22:43.283 回答
60

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

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

于 2008-09-23T13:34:21.557 回答
27

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

于 2014-01-27T22:04:46.883 回答
22

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

如果要使用参数调用基构造函数,则必须在派生构造函数中显式编写,如下所示:

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

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

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

于 2008-09-23T13:19:57.940 回答
21

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

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

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

于 2008-09-23T13:21:28.420 回答
12

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

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

于 2014-07-15T18:49:38.850 回答
10
CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }
于 2008-09-23T13:24:19.613 回答
7

当一个类派生自多个类时,没有人提到构造函数调用的顺序。在派生类时,序列如前所述。

于 2014-03-12T10:46:04.183 回答
0

如果您只是想将所有构造函数参数传递给基类(=parent),这是一个最小的示例。

这使用模板将每个带有 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;
        }
于 2020-07-02T14:35:23.917 回答