多级继承中复制构造函数的调用顺序是什么?

时间:2017-11-16 23:47:22

标签: c++

我一直试图严格地解决这个问题。鉴于三个类相互继承,序列如何工作? 例如:Classes Vehicle-> Car-> Mercedes

4 个答案:

答案 0 :(得分:1)

  

复制构造函数的调用顺序是什么

顺序与其他构造函数相同。

  • 虚拟基地
  • 非虚拟基地
  • 成员
  • 构造函数主体

如果有多个直接基地或成员,则按照声明的顺序构建。

在每个子对象构造之间,可以为下一个子对象执行初始化列表表达式。

  

例如:Classes Vehicle-> Car-> Mercedes

如果CarMercedes的基础,则在Car的构造函数体执行之前构造Mercedes base-sub-object。如果VehicleCar的基础,则在Vehicle的构造函数体执行之前构造Car base-sub-object。

答案 1 :(得分:1)

施工人员总是从下到上执行,所以首先是车辆,下一辆车,最后是梅赛德斯。 这种行为的原因是子类可能使用基类的变量,因此必须首先初始化它们。

Destructors以同样的方式回归:首先是梅赛德斯,然后是Car,最后是车辆,出于同样的原因。

答案 2 :(得分:1)

构造了显式和继承的第一个基础,然后是从左到右的顺序构造成员变量。所以,如果你有

class Base0;
class Subbase;
class Base1: Subbbase;
struct Der: Base0, Base1 {
    Der();
    Type member1;
    Type member2;
};

然后初始化的顺序是Base0 - > Subbase - > Base1 - > member1 - > member2,然后进入ctor体(到那时所有碱基和成员都已经初始化)。

析构函数反向运行:body - > member2 - > member1 - > Base1 - > Subbase - > BASE0。

答案 3 :(得分:0)

足以创建MCVE ......

#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock  HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point                 Time_t;  // std-chrono-hi-res-clk-time-point
typedef std::chrono::milliseconds           MS_t;    // std-chrono-milliseconds
typedef std::chrono::microseconds           US_t;    // std-chrono-microseconds
typedef std::chrono::nanoseconds            NS_t;    // std-chrono-nanoseconds
using   namespace std::chrono_literals;          // support suffixes like 100ms, 2s, 30us

#include <iostream>
#include <iomanip>

class Vehicle_t
{
public:
   Vehicle_t () { std::cout << "\n  Vehicle_t" << std::flush;   }
   ~Vehicle_t () = default;
};

class Car_t
   : public Vehicle_t
{
public:
   Car_t () { std::cout << "\n  Car_t" << std::flush;   }
   ~Car_t () = default;
};

class Mercedes_t
   : public Car_t
{
public:
   Mercedes_t () { std::cout << "\n  Mercedes_t" << std::flush;   }
   ~Mercedes_t () = default;
};


int main(int , char** )
{
   int retVal = -1;
   {
      Time_t start_us = HRClk_t::now();

      Mercedes_t  m;

      retVal = 0;

      auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);

      std::cout << "\n\n  duration  " << duration_us.count() << " us" << std::endl;
   }

   return(retVal);
}

带输出

Vehicle_t
Car_t
Mercedes_t

初学者注意事项:

  1. 我确定您之前已经听过,&#34;执行开始于&#39; main&#39;&#34;。这段代码也是如此。

  2. 因此声明,&#34; Mercedes_t m;&#34;是第1(对于此类层次结构)。它显示了&#34; Mercedes_t&#34;的调用。首先调用ctor(派生最多)。

  3. 请记住,所有方法(和函数)都有一个入口和一个出口。

    1. 最派生的ctor调用&#34; Car_t&#34; ctor,它调用&#34; Vehicle_t&#34;构造函数。

    2. 输出显示&#34; Vehicle_t&#34; ctor cout声明首先完成。因此,你可以推断出这个ctor首先完成。

    3. 输出显示,下一个完成的ctor是&#34; Car_t&#34;,然后&#34; Mercedes_t&#34;

    4. 摘要

      • 最后调用最基类,但最先完成。

      • 首先调用派生类最多的类,但最后完成。

      ctor Entry/Invocation sequence:   Mercedes_t -> Car_t -> Vehicle_t
      ctor Exit/ctor completion seq :   Vehicle_t  -- Car_t -- Mercedes_t
      

      update - 11/19/2017

      我认为我的大部分答案都是关于实施细节。我突然想到这样的证据&#39;我上面提供的不足。 (并且与一个或多个替代答案相矛盾)。

      更有说服力的证明&#39;是检查组件。以下是代码中的一些与我上面的MCVE不同的内容,但仍然具有可识别的名称。仅供参考 - 我的系统是Ubuntu 15.10,g ++报告(Ubuntu 5.2.1-23ubuntu1~15.10)5.2.1。

      使用gdb,我在main(即b main)设置断点,发出run,然后使用命令

        

      (gdb)反汇编/ m

      找到了

      154      {
      155         Mercedes_t  m; // invoke Mercedes_t ctor
         0x00000000004019e2 <+40>:  lea    -0x30(%rbp),%rax
         0x00000000004019e6 <+44>:  mov    %rax,%rdi
         0x00000000004019e9 <+47>:  callq  0x40214a <Mercedes_t::Mercedes_t()>
         0x00000000004019ee <+52>:  lea    -0x30(%rbp),%rax
      

      这确认了创建对象的第一个调用是 &LT; Mercedes_t :: Mercedes_t()&gt; ctor,即最先导出的ctor首先被召唤。

      接下来,从Mercedes_t ctor的相同类型的反汇编中,您可以看到对&lt; Car_t :: Car_t()&gt;。

      Dump of assembler code for function Mercedes_t::Mercedes_t():
      91     Mercedes_t () : initLineNum (log(__LINE__)) // Mercedes_t ctor init
         0x000000000040214a <+0>: push   %rbp
         0x000000000040214b <+1>: mov    %rsp,%rbp
         0x000000000040214e <+4>: sub    $0x10,%rsp
         0x0000000000402152 <+8>: mov    %rdi,-0x8(%rbp)
      => 0x0000000000402156 <+12>:    mov    -0x8(%rbp),%rax
         0x000000000040215a <+16>:    mov    %rax,%rdi
         0x000000000040215d <+19>:    callq  0x4020ec <Car_t::Car_t()>
         0x0000000000402162 <+24>:    mov    $0x5b,%edi
         0x0000000000402167 <+29>:    callq  0x401616 <log(int)>
         0x000000000040216c <+34>:    mov    %eax,%edx
         0x000000000040216e <+36>:    mov    -0x8(%rbp),%rax
         0x0000000000402172 <+40>:    mov    %edx,0x8(%rax)
      

      对我来说足够了。我强烈建议你学习gdb。

      但同样,我认为这些可能是实施细节。

      更新 - 2017年11月20日 添加了识别复制ctor序列的代码。

      我将复制ctor添加到每个班级(默认ctor之后,dtor之前):

      Vehicle_t(const Vehicle_t& /*rhs*/){
         std::cout << "\n  Vehicle_t(rhs) " << std::flush; }//Vehicle_t copy ctor (3)
      
      Car_t(const Car_t& rhs) : Vehicle_t(rhs) {
         std::cout << "\n  Car_t(rhs) " << std::flush; }  // Car_t copy ctor (3)
      
      Mercedes_t(const Mercedes_t& rhs) : Car_t(rhs) {
         std::cout << "\n  Mercedes_t(rhs) " << std::flush; }  // Mercedes_t copy ctor (3)
      

      在默认ctor之后,在&#34; retVal = 0之前为main添加了2行;&#34;

        Mercedes_t  m;    // invoke default ctor
      
        std::cout << "\n";
      
        Mercedes_t m2(m); // invoke copy ctor
      
        retVal = 0;
      

      输出现在看起来像:

        Vehicle_t
        Car_t
        Mercedes_t
      
        Vehicle_t(rhs) 
        Car_t(rhs) 
        Mercedes_t(rhs) 
      

      这与默认ctors的序列相同。

      我还使用了gdb和&#34;反汇编/ m&#34;命令并检查了总结。调用序列与默认ctor匹配。