C ++常见问题解答第20.05项:
“虚拟基类是特殊的,它们的析构函数在 最派生类'析构函数(仅)的结尾。“
我真的不明白这与典型的相符:
“首先是数据成员析构函数,然后是基类析构函数”规则
虚拟基类如何特殊?我不知道上面是什么意思:s
答案 0 :(得分:3)
你引用的书的整个段落都在描述析构函数的顺序。通常,在类声明中,为继承列出的类的顺序决定了它们的构造顺序,然后它们以相反的顺序被破坏。
虚拟基类意味着在其上使用虚拟继承:
struct Base {};
struct D : virtual Base {};
struct D1 : D, virtual Base {};
struct D2 : virtual Base, D {};
ASCII艺术提醒:
Base Base
| \ | \
/_\ \ | /_\
| \ | \
D /_\ | D
| / | /
/_\ / /_\ /_\
| / | /
D1 D2
在这种情况下,多重继承会将菱形折叠成一行。但是,这一点仍然有说明。 D1
和D2
的继承顺序无关紧要。调用D
和Base
的析构函数的顺序对于它们都是相同的。
答案 1 :(得分:3)
虚拟基类的关键属性是它们总是在派生类的任何对象中生成单个唯一基础子对象。这正是关于虚拟基类的特殊,这使得它们与常规基类不同,后者可以生成多个子对象。
例如,在此层次结构中
struct B {};
struct M1 : B {};
struct M2 : B {};
struct D : M1, M2 {}
没有虚拟继承。所有碱基都使用常规继承进行继承。在这种情况下,类D
将包含两个类型为B
的独立子对象:一个由M1
引入,另一个由M2
引入。
+-> D <-+
| |
M1 M2
^ ^
| |
B B <- there are two different `B`s in `D`
在破坏D
时正确破坏所有子对象的任务是微不足道的:层次结构中的每个类负责破坏其直接基础,并且仅负责其直接基础。这只是意味着M1
的析构函数调用其自己的B
子对象的析构函数,M2
的析构函数调用其自己的B
子对象的析构函数,而析构函数为{{1}调用其D
和M1
子对象的析构函数。
在上述销毁计划中,一切都很顺利。所有子对象都被破坏,包括M2
类型的子对象。
然而,一旦我们切换到虚拟继承,事情变得更加复杂
B
现在struct B {};
struct M1 : virtual B {};
struct M2 : virtual B {};
struct D : M1, M2 {}
中只有一个B
类型的子对象。 D
和M1
都会查看并共享类型为M2
的子对象作为其基础。
B
如果我们尝试将先前的销毁计划应用于此层次结构,我们最终将导致+-> D <-+
| |
M1 M2
^ ^
| |
+-- B --+ <- there is only one `B` in `D`
子对象被破坏两次:B
调用{{1}的析构函数子对象和M1
调用完全相同的 B
子对象的析构函数。
这当然是完全不可接受的。每个子对象必须被破坏一次且仅一次。
为了解决这个问题,当M2
和B
被用作M1
的基础子对象时,这些被明确地禁止来调用析构函数他们的M2
子对象。调用D
的析构函数的责任分配给B
的析构函数。类B
,当用作完整的独立对象(即充当大多数派生类)时,知道其中只有一个D
并知道D
的析构函数只能被调用一次。因此,类B
的析构函数将为B
类型的唯一基础子对象调用D
的析构函数。同时,B
和B
的析构函数甚至不会尝试调用M1
的析构函数。
它是如何与虚拟继承一起工作的。你所引用的规则基本上就是这样说的。那些说虚拟基地的部分&#39;最后调用析构函数只是意味着每个类的析构函数都会为其直接常规基类调用析构函数,并且只有在此之后,如果需要,它才会调用其虚拟基类(可能是间接的)。在上面的示例中,M2
的析构函数调用B
和D
的析构函数,然后才调用M1
的析构函数。