c++隐式和显式继承构造函数调用

C++ implicit and explicit inheritance constructor calls

本文关键字:继承 函数调用 c++      更新时间:2023-10-16

我有一个关于隐式和显式调用基构造函数的问题。如果我们有一个这样的类层次结构:

class Person{
    protected:
        std::string m_name;
    public:
        Person(std::string& _name) : m_name(_name){std::cout << "A person is being constructed." << std::endl;}
};
class Baby : public Person{
    private:
        int m_no_of_nappies;
    public:
        Baby(std::string& _name, int& _no_of_nappies) : m_no_of_nappies(_no_of_nappies), Person(_name) {std::cout << "A baby is being constructed." << std::endl ;}
};

根据我的课堂笔记,主要是对'Baby'的调用,如下所示:

std::string babyname = "Robert";
int nappies = 5;
Baby baby(babyname, nappies);

导致以下情况发生:

  1. 当在Baby的初始化列表中显式调用Person时:Baby的初始化列表被调用,并且no_of_nappies被初始化。
  2. 接下来,调用Person的构造函数并调用Person的初始化列表。 m_name初始化然后调用Person的构造函数体
  3. Baby的构造函数体最终被调用。

这是有意义的,但是,如果对父类的默认构造函数进行了隐式调用,那么该怎么办呢?

class Vehicle{
    protected:
        int m_no_wheels;
    public:
        Vehicle() : m_no_wheels(0) { std::cout << "A vehicle is being constructed." << std::endl; }
};
class Bicycle : public Vehicle{
    protected:
        bool m_is_locked;
    public:
        Bicycle() : m_is_locked(false) { std::cout << "A bicycle is being constructed." << std::endl; }
};

这是我不太确定的部分。我最好的猜测是,在主程序中调用Bicycle bike;具有以下效果:

  1. 从Bike隐式调用Vehicle的默认构造函数。
  2. 由于vehicle不继承任何东西, vehicle的初始化列表被称为,它将m_no_wheels初始化为0
  3. 调用车辆的构造函数体。
  4. 我们返回到Bicycle和,现在它的初始化列表被称为,将m_is_locked初始化为false
  5. Bike的构造函数体被调用

谁能解释一下我在隐含调用背后的推理是否正确?

在我看来,主要的区别在于,对于基构造函数的显式引用,子类的初始化列表总是首先被命中,以便调用该基构造函数——然而,对于隐式调用,最上面的父类的初始化列表总是首先被命中。

谢谢你,非常感谢!

Edit:我特别问如果顺序改变,取决于对父类的隐式或显式调用。

基类和成员的初始化顺序在[class.base]中指定。您可以在这里找到摘要:http://en.cppreference.com/w/cpp/language/initializer_list#Initialization_order

成员初始化式在列表中的顺序无关紧要:初始化的实际顺序如下:

  1. 如果构造函数是用于派生最多的类,则虚拟基类将按照它们在基类声明的深度优先从左到右遍历中出现的顺序进行初始化(从左到右指基类说明符列表中的出现顺序)
  2. 然后,直接基类按从左到右的顺序初始化,因为它们出现在该类的基说明符列表
  3. 然后,非静态数据成员按照类定义中的声明顺序初始化。
  4. 最后,执行构造函数体

(注意:如果初始化顺序由不同构造函数的成员初始化列表中的外观控制,则析构函数将无法确保析构顺序与构造顺序相反)

初始化顺序在定义任何构造函数之前就已经确定;构造函数初始化列表只影响如何初始化基和成员,而不影响它们初始化的顺序。

因为PersonBaby的基,所以它总是在Baby的成员m_no_of_nappies之前被初始化。作为Person初始化的一部分,初始化它自己的成员,然后执行它的构造函数体。在Person的构造函数体返回后,对m_no_of_nappies进行初始化。(破坏总是以相反的顺序发生。)Vehicle同样是Bicycle的基数,并且首先初始化;由于它没有mem初始化式,因此调用默认构造函数

§12.6.2定义了初始化的方式:

成员初始化式在列表中的顺序无关紧要:实际的初始化顺序如下:

  • 如果构造函数是为派生最多的类,虚基类是按照深度优先的顺序初始化的吗基类声明的从左到右遍历(从左到右)指的是在基本规格表中的外观)
  • 然后,直接基地类在其中出现时按从左到右的顺序初始化类的基本说明符列表
  • 则是非静态数据成员按类定义中的声明顺序初始化。
  • 最后,构造函数体被执行(注意:如果初始化顺序的成员初始化项列表中的外观控制不同的构造函数,那么析构函数就不能保证毁灭的顺序与…的顺序相反建设)

对您的案例进行总结(不考虑虚函数):

  1. 基类按照声明继承的顺序
  2. 按申报顺序排列的成员

因此构造函数初始化列表中的顺序对不起作用。

在第一种情况下,你在这一点上是错误的:PersonBaby的基类,并且在m_no_of_nappies之前初始化


编辑:你的问题

Baby从它的初始化列表中调用Person,因此第一个被击中的是Baby的初始化列表?

[class.base。/10可能是你正在寻找的:你没有真正"调用"基类构造函数(假设没有委托),它是由编译器在初始化派生对象时为你调用的。

编译器为您设置了一些东西,以帮助保持构造函数和析构函数的正确顺序

忽略初始化项顺序的原因是为了保持构造函数和析构函数调用通常的FIFO顺序。允许两个构造函数使用不同的基和成员初始化顺序,将限制实现使用更动态和更昂贵的策略

摘自https://stackoverflow.com/a/24287946/1938163

最后

对基类的隐式调用(编译器做的)是在Bicycle的初始化列表之前还是之后完成的?

在第12.6.2节其他成员类初始化之前。