将继承更改为虚拟的后果?
Consequences of changing inheritance to virtual?
我正在做一个我还没有开始的大项目。我的任务是在已经存在的内容中添加一些额外的功能。我处于必须使用虚拟继承的情况,因为我有一个钻石模型。下图描述了这种情况:
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
不是派生最多的类,则会忽略B
中B
的初始值设定项:
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] 中找不到其他对虚拟继承的引用。
- 虚拟决赛作为安全
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- std::is_trivially_copyable_v 关于虚拟功能
- 删除C++继承中虚拟类成员的代码重复
- 子类地址等于虚拟基类地址?
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 用于创建/注册虚拟存储设备的 IOKit 驱动程序
- 将继承更改为虚拟的后果?