将继承更改为虚拟的后果?

Consequences of changing inheritance to virtual?

本文关键字:后果 虚拟 继承      更新时间:2023-10-16

我正在做一个我还没有开始的大项目。我的任务是在已经存在的内容中添加一些额外的功能。我处于必须使用虚拟继承的情况,因为我有一个钻石模型。下图描述了这种情况:

Base class
/           
/             
My new class      A class that was there before (OldClass)
             /
           /
         /
       /
My other new class

为此,中间的两个类都必须通过public virtual从基继承,而不仅仅是public。所以:

class OldClass: public BaseClass {}

必须成为:

class OldClass: public virtual BaseClass {}

由于这个项目真的很大,而且我正在处理其中的一小部分,我不想通过这样做来破坏它。我的临时测试有效,程序似乎工作正常,但我仍然持怀疑态度。

所以我的问题是:添加virtual关键字应该期待什么副作用和后果?有什么好担心的吗?

直接后果是,对于常规继承,派生类调用直接基的构造函数,而对于虚拟继承,派生最多的类(即直接实例化的类)会调用,因为这是唯一知道所有虚拟基的地方。

比较:

struct A { A(int) { } };
struct B : A { B(int i) : A(i) { } };
struct C : B { C(int i) : B(i) { } };

struct A { A(int) { } };
struct B : virtual A { B(int i) : A(i) { } };
// wrong: struct C : B { C(int i) : B(i) { } };
struct C : B { C(int i) : A(i), B(i) { } }; // correct

此外,初始值设定项的行为也不同,因为如果A不是派生最多的类,则会忽略BB的初始值设定项:

struct A { A(int i) { cout << 'A' << i; } };
struct B : virtual A { B(int i) : A(i+1) { cout << 'B' << i; } };
struct C : B { C(int i) : A(i+1), B(i+1) { cout << 'C' << i; } };
A a(0);        // prints A0
B b(0);        // prints A1B0
C c(0);        // prints A1B1C0

如果此处有非虚拟继承(这将强制您删除C构造函数中的A初始值设定项,则第三行将输出A2B1C0

除了 Simon Richter 所说的调用构造函数之外,使用虚拟继承意味着你应该对强制转换更加小心: 每当在包含虚拟继承的层次结构中向下转换指针时,都需要使用dynamic_cast<>,因为基本对象和强制转换的目标类型之间的相对偏移量取决于对象的具体实际类型。除此之外,其他一切都应该按预期工作。

这很难以这种抽象的方式回答,因为这完全取决于类在做什么以及你如何使用它们。

拥有虚拟继承意味着您的两个中产阶级将共享相同的Base。这就是你想要的吗?

没有语言规则反对在层次结构中实际具有两个单独的Base类。调用成员函数有点困难,因为您必须通过在函数名称p->NewClass::base_function()p->OldClass::base_function();前缀来显式指示要调用的副本。如果您不需要共享Base数据,则可以使用。

就像Serge说的,如果其他类只继承一个Base,它仍然只包含一个Base。

根据标准,虚拟继承与非虚拟继承完全相同,只是派生对象中仅存在虚拟继承类的一个实例。

因此,在原始代码中没有任何内容对派生自Base的类具有多重继承,将Base的继承更改为虚拟不应该改变行为。但是您必须咨询构建类层次结构以确保它。


参考来自 n4096 草案:

10.1 多个基类 [class.mi]
...
4 不包含关键字 virtual 的基类说明符指定非虚拟基类。基地 包含关键字 virtual 的类说明符,指定虚拟基类。对于每个不同的匹配项 在派生次数最多的类的类格中的非虚基类中,派生最多的对象 (1.8) 应 包含该类型的相应不同基类子对象。对于每个不同的基类,即 指定的虚拟对象应包含该类型的单个基类子对象。

除了该段后面的示例外,我在 [class.mi] 中找不到其他对虚拟继承的引用。