使用虚拟的破坏顺序

时间:2010-08-17 21:29:18

标签: c++ destructor virtual-destructor

当我使用虚拟功能时,有人可以帮助解决破坏的顺序。它是从基类开始,然后是派生类吗?

8 个答案:

答案 0 :(得分:10)

由于我没有看到虚函数如何更改任何对象的销毁顺序,我假设您指的是虚拟 继承中基类和数据成员的销毁顺序 场景。

子对象 构建

  1. 基类的构建从大多数基础到大多数派生;
  2. 多个基类以其声明的顺序构建为基类;
  3. 虚拟基类在所有其他之前构建,它们之间遵守上述两条规则;
  4. 数据成员是在执行封闭对象的构造函数的主体之前构建的,按照它们的声明顺序。
  5. 销毁 构建完全相反,因此您只需记住上述内容即可。

    然而,上述四条规则是按顺序排列的,因为这是有道理的,如果你理解为什么这个顺序有意义,你甚至不必记住这四条规则,但可以从你的理解中推断它们(因为我只是所做的那样)。那么让我们检查一下这个顺序:

    • 您可能希望使用基类从派生类的构造函数提供的任何服务。当然,在实际构造之前,不能使用(基础)类对象。因此,在构造派生类时,需要已经构造了基类。 (顺便提一下,这也解释了为什么虚函数调度不能完全在构造函数中工作:构造子对象时,只构造了基类的子对象;派生类的子对象还没有因此,不能将对虚函数的调用分派给派生类。一如既往,析构函数是相同的,只是后退。)
    • 由于多个基类是相同的兄弟,所以必须随意挑选一些订单。最终,声明的顺序是最简单的使用顺序。数据成员也是相同的兄弟姐妹,遵循相同(或多或少任意)的声明顺序规则。
    • 虚拟基类是奇怪的野兽。因为永远只有一个虚拟基类的子对象,所以有一个特殊规则表明它总是需要首先构造,直接来自最派生类的构造函数。 (这就是为什么虚拟基类最适合作为抽象基类,没有数据,只有默认构造函数。)

答案 1 :(得分:5)

假设您已将析构函数正确声明为虚拟。

然后以完全相反的构造顺序完成破坏。

一般来说,这将是:

A)从派生程度最高的班级开始 B)递归地重复以下步骤。

1)执行析构函数代码 2)执行每个成员的析构函数(按创建的相反顺序)
3)执行父类的析构函数。 (如果创建的顺序不止一个)

如果你使用虚拟继承,那么事情会略有不同,因为基类构造的顺序与正常情况不同。 但是破坏的顺序总是与施工顺序相反。

答案 2 :(得分:1)

销毁订单是向后的施工订单。我最近制作了一个小工具来显示任何层次结构的构造顺序。看这里:

在图表中,数字较小的节点构造为第一,并且最后

答案 3 :(得分:1)

第12.6.2 / 5节:

  

初始化应按以下顺序进行:

     
      
  • 首先,仅适用于派生程度最高的类的构造函数   如下所述,虚拟基类应在中初始化   命令它们出现在深度优先从左到右的遍历中   基类的有向非循环图,其中“从左到右”是   派生类中基类名称的出现顺序   基说明符列表。
  •   
  • 然后,应初始化直接基类   按声明顺序显示在base-specifier-list中   (无论mem-initializers的顺序如何)。
  •   
  • 然后,非静止的   数据成员应按其声明的顺序进行初始化   类定义(再次无论顺序如何   MEM-初始化)。 - 最后,执行构造函数的主体。
  •   
     

[注意:声明命令的目的是确保基础和   成员子对象以相反的顺序销毁   初始化。 ]

答案 4 :(得分:0)

虚拟函数对破坏的顺序没有影响,另一方面,虚拟基类也没有区别。

没有虚拟基类,派生类总是在其基类之前被销毁;这是它们构造的相反顺序。

对于大多数派生类,首先构造虚拟基类,然后在其他基类之前和最派生类本身之前构造虚拟基类。销毁以相反的顺序发生。这意味着虚拟基础可能会在实际派生它的类之后被销毁,如果该类不是被销毁的最派生类。对于直接基类,这种情况永远不会发生。

答案 5 :(得分:0)

这与构造函数相反。首先派生出来。

答案 6 :(得分:0)

从下往上的破坏顺序。 (从派生到基础)

  

简短回答:恰恰相反   构造函数命令。

     

答案很长:假设“最多   派生的“阶级是D,意思是   最初的实际对象   创造了D级,D   继承乘法(和非虚拟)   来自B1和B2。子对象   对应于大多数派生的D类   首先运行,然后是dtors   它的非虚拟基类   反向申报单。就这样   析构函数顺序为D,B2,B1。   此规则以递归方式应用;对于   例如,如果B1继承自B1a和   B1b和B2继承自B2a和B2b,   最后的订单是D,B2,B2b,B2a,   B1,B1b,B1a。

请参阅c++ faq section 25

答案 7 :(得分:0)

首先派生,然后是基础。非虚拟案例没有区别。

补充说明。当你有继承和虚方法时,你必须将析构函数声明为虚拟,否则你可以删除未定义的行为。

示例,假设Derived派生自Base,并使用以下行分配Derived:

Base *o = new Derived();
delete(o);

如果您的代码中出现这种情况,并且Base没有虚拟析构函数,则结果行为是未定义的。通常,只会调用Base的析构函数。 Derived的析构函数不会被调用,因为你在Base指针上调用delete。但是,该程序可能会崩溃。一旦你处于未定义行为的领域,所有的赌注都会关闭,你的运行代码就会注定失败。为了防止混乱,Base析构函数必须是虚拟的。