【问题标题】:C++ implicit and explicit inheritance constructor callsC++ 隐式和显式继承构造函数调用
【发布时间】:2015-05-19 18:33:07
【问题描述】:

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

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。
  3. 然后调用 Person 的构造函数体。
  4. 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 的默认构造函数进行了隐式调用。 在调用 Bike 的初始化列表之前。
  2. 由于车辆不继承任何东西,车辆的初始化列表被调用,它将m_no_wheels初始化为0。
  3. Vehicle 的构造函数体被调用。
  4. 我们返回到 Bicycle,现在调用它的初始化列表,将 m_is_locked 初始化为 false。
  5. Bike 的构造函数体被调用。

有人可以解释一下我隐式调用背后的推理是否正确吗?

在我看来,主要区别在于,在显式引用基础构造函数时,总是首先点击子类的初始化列表才能调用基础构造函数 - 但是,通过隐式调用,最顶部的父级初始化列表总是最先被命中。

谢谢,非常感谢!

编辑:我特别询问订单是否更改,具体取决于对父类的隐式或显式调用。

【问题讨论】:

    标签: c++ inheritance implicit explicit


    【解决方案1】:

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

    列表中成员初始化器的顺序无关:实际初始化顺序如下:

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

    (注意:如果初始化顺序是由不同构造函数的成员初始化列表中的出现来控制的,那么析构函数将无法保证销毁顺序与构造顺序相反)

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

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

    【讨论】:

    • 这很有意义。只是为了详细说明:这是否意味着即使在 m_no_of_nappies 运行之前调用了 Person 的初始化程序和构造函数 - Baby 的初始化列表仍然被命中,以便调用 Person 的初始化程序。那么,Baby 从其初始化列表中调用 Person,因此首先命中的是 Baby 的初始化列表?
    • @MuyiwaOlu 我不确定我是否理解你的问题。我不知道你所说的“击中”是什么意思。基和成员按规定的顺序进行初始化,在初始化期间,如果有相应的 mem-initializer 可用,则将其用于命名的基或成员的初始化。
    • @MuyiwaOlu 如果您没有在派生类数据初始化列表之前列出基类构造函数,您可能会收到取决于编译器设置的警告。
    • @Brian 我所说的命中是指Person 的初始化列表,因此是从Baby 的初始化列表中调用的正文?
    • @MuyiwaOlu 一个初始化列表并没有真正从另一个初始化列表中调用。 Baby 的初始化列表确定调用 Person 的哪个构造函数,当调用该构造函数时,相应的初始化列表控制 Person 成员的初始化。
    【解决方案2】:

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

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

    • 如果构造函数用于最派生类,则为虚拟基类 按照深度优先出现的顺序进行初始化 基类声明的从左到右的遍历(从左到右 指在基本说明符列表中的出现)
    • 然后,直接基地 类按照从左到右的顺序初始化,因为它们出现在 类的基本说明符列表
    • 然后,非静态数据成员是 按类定义中的声明顺序初始化。
    • 最后, 构造函数的主体被执行(注意:如果初始化顺序 由成员初始值设定项列表中的外观控制 不同的构造函数,那么析构函数将无法确保 破坏的顺序与破坏的顺序相反 建设)

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

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

    因此构造函数初始化列表中的顺序无效。

    在第一种情况下你错了:Person 是Baby 的基类,在m_no_of_nappies 之前被初始化


    编辑: 你的问题

    Baby 从其初始化列表中调用 Person,因此首先被击中的是 Baby 的初始化列表?

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

    编译器会为您进行设置以帮助保持构造函数和析构函数的正确顺序

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

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

    最后

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

    在 §12.6.2 中的其余成员类初始化之前。

    【讨论】:

    • 感谢您对我的关心。第二个隐含的问题呢?
    • @MuyiwaOlu [class.base.init]/10 可能是您正在寻找的。您并没有真正“调用”基类构造函数(假设没有委托),它是由编译器在初始化派生对象时为您调用的
    • 我只是想知道对基类的隐式调用(编译器所做的)是在Bicycle的初始化列表之前还是之后完成的?
    • 在其余的成员类初始化之前。
    • 您能否将其添加到帖子中,以便我将其标记为答案?谢谢!
    猜你喜欢
    • 2018-10-10
    • 2012-08-13
    • 1970-01-01
    • 2015-10-18
    • 2017-05-20
    • 1970-01-01
    • 2019-05-14
    • 1970-01-01
    • 2016-03-12
    相关资源
    最近更新 更多