我正在开展一项我没有开始的大型项目。我的任务是为已有的功能添加一些额外的功能。我必须使用虚拟继承,因为我有一个钻石模型。情况如下图所示:
Base class
/ \
/ \
My new class A class that was there before (OldClass)
\ /
\ /
\ /
\ /
My other new class
为了实现这一点,中间have to中的两个类都从public virtual
继承而不仅仅是public
。所以:
class OldClass: public BaseClass {}
必须成为:
class OldClass: public virtual BaseClass {}
由于这个项目非常庞大,而且我只从事其中的一小部分工作,所以我不想通过这样做来打破它。我的adhoc测试工作,程序似乎工作正常,但我仍然怀疑。
所以我的问题是:添加virtual
关键字时,我应该期待哪些副作用和后果?有什么值得担心的吗?
答案 0 :(得分:7)
直接后果是,对于常规继承,派生类调用直接基类的构造函数,而对于虚拟继承,最派生类(即直接实例化的类)确实如此,因为这是唯一知道的地方所有虚拟基地。
比较
struct A { A(int) { } };
struct B : A { B(int i) : A(i) { } };
struct C : B { C(int i) : B(i) { } };
VS
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
如果您在此处有非虚拟继承(这会强制您删除A
构造函数中的C
初始值设定项,则第三行将输出A2B1C0
。
答案 1 :(得分:3)
除了Simon Richter关于调用构造函数的内容之外,使用虚拟继承意味着你应该对你的强制转换更加小心:每当你在层次结构中向下转换指针时,你需要使用dynamic_cast<>
包括虚拟继承,因为基础对象和强制转换的目标类型之间的相对偏移取决于对象的具体实际类型。除此之外,其他一切都应该按预期工作。
答案 2 :(得分:1)
这很难以这种抽象的方式回答,因为它取决于类正在做什么以及如何使用它们。
拥有虚拟继承意味着您的两个中间类将共享相同的Base
。这就是你想要的吗?
没有语言规则反对在层次结构中实际拥有两个单独的Base
类。调用成员函数要困难一些,因为您必须通过在函数名称p->NewClass::base_function()
或p->OldClass::base_function();
前添加前缀来明确指出要调用的副本。如果共享Base
数据不是您需要的,那么这是有效的。
和Serge说的一样,如果其他一些类只继承了一个Base
,它仍然只包含一个Base。
答案 3 :(得分:0)
根据标准,虚拟继承与非虚拟继承完全相同,只是在派生对象中只存在一个virtualy继承类的单个实例。
因此,原始代码中没有任何内容对从Base
派生的类具有多重继承,将Base
的继承更改为虚拟不应更改行为。但你必须参考建立类层次以确保它。
参考n4096草案:
10.1多个基类[class.mi]
...
4不包含关键字virtual的基类说明符指定非虚基类。一个基地 包含关键字virtual的类说明符,指定虚拟基类。对于每个不同的事件 在最派生类的类格子中的非虚基类,最派生的对象(1.8)应该是 包含该类型的相应的不同基类子对象。对于每个不同的基类 指定虚拟,最派生的对象应包含该类型的单个基类子对象。
除了该段后面的例子,我在[class.mi]中找不到对虚拟继承的其他引用。