10

当我使用虚函数时,有人可以帮助我破坏的顺序是什么。它是从基类开始,然后是派生类吗?

4

8 回答 8

14

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

子对象被构造

  1. 基类从最基础到最派生的
  2. 多个基类按其声明为基类的顺序构造;
  3. 虚拟基类在所有其他类之前构建,它们之间遵循上述两条规则;
  4. 数据成员在封闭对象的构造函数主体执行之前构造的,按照它们的声明顺序。

Destruction与Construction正好相反,所以你只需要记住上面的内容。

但是,上面的四个规则是按这个顺序排列的,因为这很有意义,如果你理解为什么这个顺序有意义,你甚至不必记住这四个规则,而是可以根据你的理解推断它们(就像我刚刚做的那样)。所以让我们检查一下这个顺序:

  • 您可能想使用基类从派生类的构造函数中提供的任何服务。当然,在实际构造之前,您不能使用(基)类对象。因此,在构造派生类时,需要已经构造了基类。(顺便说一句,这也解释了为什么虚函数调度不能完全在构造函数中工作:当一个子对象被构造时,只有基类的子对象已经被构造;派生类的子对象还没有被构造构造。因此,对虚函数的调用不得分派给派生类。与往常一样,析构函数是相同的,只是倒过来。)
  • 由于多个基类是相同的兄弟,因此必须任意选择一些顺序。最终,声明的顺序是最简单的一种。数据成员也是相同的兄弟,遵循相同的(或多或少任意的)声明顺序规则。
  • 虚拟基类是奇怪的野兽。因为总是只有一个虚拟基类的子对象,所以有一条特殊的规则说它总是需要首先构造,从最派生类的构造函数开始。(这就是为什么虚拟基类最适合作为没有数据且只有默认构造函数的抽象基类。)
于 2010-08-17T21:43:56.240 回答
5

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

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

一般来说,这将是:

A) 从最派生的类开始。
B) 递归地重复以下操作。

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

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

于 2010-08-17T21:31:53.420 回答
1

第 12.6.2/5 节:

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

  • 首先,并且仅对于如下所述的最派生类的构造函数,虚拟基类应按照它们在基类的有向无环图的深度优先从左到右遍历中出现的顺序进行初始化,其中“左-to-right”是派生类基说明符列表中基类名称的出现顺序。
  • 然后,直接基类应按照它们出现在 base-specifier-list 中的声明顺序进行初始化(不管 mem-initializers 的顺序如何)。
  • 然后,非静态数据成员应按照它们在类定义中声明的顺序进行初始化(同样不管 mem-initializers 的顺序)。— 最后,构造函数的主体被执行。

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

于 2010-08-18T09:19:56.703 回答
1

破坏顺序是倒过来的构造顺序。我最近制作了一个小工具来显示任何层次结构的构造顺序。看这里:

在图中,编号较小的节点首先构建,最后销毁。

于 2010-08-17T21:32:19.377 回答
0

虚函数对破坏的顺序没有影响,而虚基类则相反。

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

对于最派生类,首先构造虚拟基类,在其他基类之前和最派生类本身之前。破坏以相反的顺序发生。这意味着如果该类不是被销毁的最派生类,则虚拟基可能会在从其虚拟派生的类之后被销毁。对于直接基类,这永远不会发生。

于 2010-08-17T21:31:14.450 回答
0

它与构造函数相反。所以先派生。

于 2010-08-17T21:31:21.680 回答
0

如果是自下而上的破坏顺序。(从派生到基础)

简短的回答:与构造函数顺序完全相反。

长答案:假设“最衍生”的类是 D,这意味着最初创建的实际对象属于 D 类,并且 D 从 B1 和 B2 继承乘法(非虚拟地)。对应于大多数派生类 D 的子对象首先运行,然后是其非虚拟基类的 dtor,以相反的声明顺序运行。因此,析构函数的顺序是 D、B2、B1。该规则是递归应用的;例如,如果 B1 继承自 B1a 和 B1b,而 B2 继承自 B2a 和 B2b,则最终顺序为 D、B2、B2b、B2a、B1、B1b、B1a。

请参阅C++ 常见问题解答第 25 节

于 2010-08-17T21:32:00.033 回答
0

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

附加说明。当您有继承和虚拟方法时,您必须将析构函数声明为虚拟,否则在删除时可能会出现未定义的行为。

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

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

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

于 2010-08-17T21:33:29.333 回答