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

lev*_*vik 650 c++ inheritance constructor

从子类1调用超类构造函数的C++规则是什么?

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

luk*_*uke 887

如果没有参数,则会自动为您调用基类构造函数.如果要使用参数调用超类构造函数,则必须使用子类的构造函数初始化列表.与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
        }
};
Run Code Online (Sandbox Code Playgroud)

有关构造函数初始化列表的更多信息,请参见此处此处.

  • 我从SuperClass构造函数中删除了'explicit'.尽管是单参数构造函数的最佳实践,但它与手头的讨论没有密切关系.有关显式关键字的更多信息,请参阅:http://weblogs.asp.net/kennykerr/archive/2004/08/31/Explicit-Constructors.aspx (47认同)
  • @hagubear,仅对构造函数AFAIK有效 (3认同)
  • @Gnuey,注意`:SuperClass(foo)`部分.`foo`显式传递给超类的构造函数. (2认同)

pue*_*tzk 225

在C++中,在进入构造函数之前,会为您调用所有超类和成员变量的无参数构造函数.如果你想传递它们的参数,有一个单独的语法称为"构造函数链接",它看起来像这样:

class Sub : public Base
{
  Sub(int x, int y)
  : Base(x), member(y)
  {
  }
  Type member;
};
Run Code Online (Sandbox Code Playgroud)

如果此时运行的任何东西抛出,之前完成构造的基础/成员都会调用它们的析构函数,并且异常将被重新发送给调用者.如果要在链接期间捕获异常,则必须使用函数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;
};
Run Code Online (Sandbox Code Playgroud)

在这种形式中,请注意try块函数的主体,而不是在函数体内; 这允许它捕获由隐式或显式成员和基类初始化以及函数体期间抛出的异常.但是,如果函数catch块没有抛出不同的异常,则运行时将重新抛出原始错误; 初始化期间的异常不能被忽略.

  • 感谢您为初始化程序包含try/catch语法.我已经使用C++ 10年了,这是我第一次看到它. (97认同)
  • 我不得不承认,长时间使用C++,这是我第一次在构造函数列表中看到try/catcn. (17认同)
  • 是.我已经重写了该部分,并修复了一个错误(try关键字在初始化列表之前).我本来应该查一下而不是从内存中写字,这不是经常使用的东西:-) (2认同)

Dim*_*ima 51

在C++中,有一个构造函数初始化列表的概念,在这里您可以并且应该调用基类的构造函数,并且您还应该在其中初始化数据成员.初始化列表位于冒号后面的构造函数签名之后,并且位于构造函数的主体之前.假设我们有一个A类:


class A : public B
{
public:
  A(int a, int b, int c);
private:
  int b_, c_;
};
Run Code Online (Sandbox Code Playgroud)

然后,假设B有一个带有int的构造函数,A的构造函数可能如下所示:


A::A(int a, int b, int c) 
  : B(a), b_(b), c_(c) // initialization list
{
  // do something
}
Run Code Online (Sandbox Code Playgroud)

如您所见,在初始化列表中调用基类的构造函数.顺便说一下,初始化初始化列表中的数据成员比在构造函数体内分配b_和c_的值更可取,因为您节省了额外的赋值成本.

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

  • 不.初始化和分配是不同的事情.当调用构造函数时,它将尝试使用它认为是默认值的任何内容初始化每个数据成员.在init列表中,您可以提供默认值.因此,无论哪种情况,都会产生初始化成本. (5认同)
  • 这个答案很有用,因为它显示了语法变体,其中一个具有标题和源文件,并且一个不希望标题中的初始化列表.非常有帮助,谢谢. (2认同)

TT_*_*TT_ 21

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

  • 这个答案有点令人困惑,尽管我已经阅读了几次,并看了一下问题的链接.我认为它所说的是如果你在派生类的主体中使用对父类或超类构造函数的显式调用,这实际上只是创建父类的实例而它不调用父类派生对象上的构造函数.在派生类的对象上调用父类或超类构造函数的唯一方法是通过初始化列表而不是派生类构造函数体. (11认同)

Nil*_*nck 19

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

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

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

class derived : public base
{
  public:
  derived () : base (number)
  {
  }
};
Run Code Online (Sandbox Code Playgroud)

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


CR.*_*CR. 19

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

Class2::Class2(string id) : Class1(id) {
....
}
Run Code Online (Sandbox Code Playgroud)

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


edW*_*edW 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;
}
Run Code Online (Sandbox Code Playgroud)

输出为:1


Dyn*_*ite 9

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


Kri*_*Oza 7

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

  • @EJP因为问题是关于调用规则,所以值得一提的是在答案中调用的顺序 (3认同)
  • 如果没有人谈论它,它在哪里提到? (2认同)

Mar*_*hke 7

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

它使用模板将带有 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;
}
Run Code Online (Sandbox Code Playgroud)

输出

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)
Run Code Online (Sandbox Code Playgroud)

更新:使用可变参数模板

推广到 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;
        }
Run Code Online (Sandbox Code Playgroud)

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

  • 我确实对这样好的例子建议在任何地方使用“std::endl”感到有点冒犯。人们看到这一点并将其放入循环中,并想知道为什么“在 C++ 中”将一堆行写入文本文件比使用“fprintf”慢 5 倍到 20 倍。TL;DR:使用 `"\n"` (添加到现有的字符串文字中,如果有的话),并仅在需要将缓冲区刷新到文件时使用 `std::endl` (例如,在代码崩溃时进行调试)并且您想看到它的最后一句话)。我认为 `std::endl` 是一个为了方便而设计的错误:一个很酷的“小工具”,它的功能远比名字所暗示的要多。 (4认同)