【问题标题】:What is the order in which the destructors and the constructors are called in C++C++中调用析构函数和构造函数的顺序是什么
【发布时间】:2010-10-13 20:33:11
【问题描述】:

C++ 中调用析构函数和构造函数的顺序是什么?使用一些基类和派生类的例子

【问题讨论】:

    标签: c++ constructor destructor


    【解决方案1】:

    顺序是:

    1. 基础构造函数
    2. 派生构造函数
    3. 派生的析构函数
    4. 基础析构函数

    示例:

    class B
    {
    public:
      B()
      {  
        cout<<"Construct B"<<endl;
      }
    
      virtual ~B()
      {
        cout<<"Destruct B"<<endl;
      }
    };
    
    class D : public B
    {
    public:
      D()
      {  
        cout<<"Construct D"<<endl;
      }
    
      virtual ~D()
      {
        cout<<"Destruct D"<<endl;
      }
    };
    
    
    
    int main(int argc, char **argv)
    {
      D d; 
      return 0;
    }
    

    示例输出:

    构造B

    构造D

    破坏D

    破坏B

    多层继承就像一个堆栈:

    如果您考虑将一个项目压入堆栈作为构造,并将其取出作为破坏,那么您可以将多个层次的继承视为堆栈。

    这适用于任意数量的级别。

    示例 D2 派生自 D 派生自 B。

    将 B 压入堆栈,将 D 压入堆栈,将 D2 压入堆栈。所以施工顺序是B、D、D2。然后找出破坏顺序开始弹出。 D2、D、B

    更复杂的例子:

    更复杂的例子请看@JaredPar提供的链接

    【讨论】:

      【解决方案2】:

      C++ FAQ Lite 中提供了这些事件的详细描述,包括虚拟继承和多重继承。第 25.14 和 25.15 节

      https://isocpp.org/wiki/faq/multiple-inheritance#mi-vi-ctor-order

      【讨论】:

      • 提供的链接已失效。
      【解决方案3】:

      另外,请记住,虽然数组元素是先构造的 -> 最后一个,但它们会以相反的顺序被破坏:最后一个 -> 第一。

      【讨论】:

      • +1 几乎所有事情都是如此。破坏的秩序总是与建设相反。静态变量没有保证的构造顺序,但是破坏会以相反的顺序发生。
      • 这是(预期的)模板化容器行为和/或内置 new [] / delete [] 行为吗?
      【解决方案4】:

      我必须补充以前的答案,因为每个人似乎都忽略了它

      当你有一个派生类实例被创建时,确实构造函数中的代码basebefore 将在 inside 派生 的构造函数中调用,但请记住,派生在技术上仍然是“创建”基础之前

      当你调用派生类析构函数时,确实内部的代码调用派生析构函数代码基析构函数之前,但也要记住base销毁 派生之前。

      当我说created/destroyed时,我实际上指的是allocated/deallocated

      如果您查看这些实例的内存布局,您会发现派生实例组成基本实例。例如:

      派生的内存:0x00001110 to 0x00001120

      基础内存:0x00001114 到 0x00001118

      因此,派生类必须在构造中基础之前分配。并且派生类必须在销毁的基础之后被释放。

      如果你有以下代码:

      class Base 
      {
      public:
          Base()
          {
              std::cout << "\n  Base created";
          }
          virtual ~Base()
          {
              std::cout << "\n  Base destroyed";
          }
      }
      
      class Derived : public Base 
      {
      public:
          Derived()
          // Derived is allocated here 
          // then Base constructor is called to allocate base and prepare it
          {
              std::cout << "\n  Derived created";
          }
          ~Derived()
          {
              std::cout << "\n  Derived destroyed";
          }   
          // Base destructor is called here
          // then Derived is deallocated
      }
      

      因此,如果您创建了 Derived d; 并使其超出范围,那么您将在 @Brian 的答案中获得输出。但是内存中的对象行为并不是真正的顺序,更像是这样:

      建筑:

      1. 派生分配

      2. 基数分配

      3. 调用基础构造函数

      4. 调用的派生构造函数

      破坏:

      1. 调用的派生析构函数

      2. 基析构函数调用

      3. 基址解除分配

      4. 派生解除分配

      【讨论】:

      • 这是一个很好的说明。首先为派生类分配内存,然后是基类,但构造首先为基类发生,然后是派生类。对于销毁,最后一个添加到“堆栈”的对象被销毁,即,派生类,然后是基类。但释放的顺序相反,先是基数,然后是派生数。
      • 你知道为什么会这样吗?这对我来说很有趣。
      • @nnrales 这遵循组合关系。例如,当您在类中声明std::string(不是指针)时,字符串也在作曲家之后分配,但其构造函数在之前调用。派生类组成基类并将其包含在其内存空间中,因此必须首先分配派生类。如果您打印地址,这将更清楚。您甚至可以通过将派生的this 转换为基实例来访问基实例,并使用它来执行一些操作。
      【解决方案5】:

      order-dtors-for-members 对此进行了明确描述。基本上,规则是“先构建,后销毁”。

      构造函数调用顺序:

      1. base 的构造函数在“:”之后按出现顺序调用
      2. 派生类成员的构造函数按出现顺序在类的构造函数之前调用

      析构函数的调用顺序与构造函数的调用顺序相反。

      例子:

      #include <iostream>
      
      struct base0 {  base0(){printf("%s\n", __func__);};~base0(){printf("%s\n", __func__);}; };
      struct base1 { base1(){printf("%s\n", __func__);}; ~base1(){printf("%s\n", __func__);};};
      struct member0 { member0(){printf("%s\n", __func__);};  ~member0(){printf("%s\n", __func__);};};
      struct member1 { member1(){printf("%s\n", __func__);}; ~member1(){printf("%s\n", __func__);};};
      struct local0 { local0(){printf("%s\n", __func__);}; ~local0(){printf("%s\n", __func__);}; };
      struct local1 { local1(){printf("%s\n", __func__);};  ~local1(){printf("%s\n", __func__);};};
      struct derived: base0, base1
      {
        member0 m0_;
        member1 m1_;
        derived()
        {
          printf("%s\n", __func__);
          local0 l0;
          local1 l1;
        }
        ~derived(){printf("%s\n", __func__);};
      };
      int main()
      {
        derived d;
      }
      

      输出:

      base0
      base1
      member0
      member1
      derived
      local0
      local1
      ~local1
      ~local0
      ~derived
      ~member1
      ~member0
      ~base1
      ~base0
      

      【讨论】:

        猜你喜欢
        • 2012-04-10
        • 2013-06-24
        • 2016-03-18
        • 1970-01-01
        • 2011-01-16
        • 2011-11-24
        相关资源
        最近更新 更多