这是一些真实代码的简化,当我没有意识到其他人已经实现了Foo并从中衍生出来时,我犯了一个真正的错误。
#include <iostream>
struct Base {
virtual ~Base() { }
virtual void print() = 0;
};
struct OtherBase {
virtual ~OtherBase() { }
};
struct Foo : public Base { // better to use virtual inheritance?
virtual void print() { std::cout << "Foo" << std::endl; };
};
struct Bar : public Base { // better to use virtual inheritance?
virtual void print() { std::cout << "Bar" << std::endl; };
};
// The design is only supposed to implement Base once, but I
// accidentally created a diamond when I inherited from Bar also.
class Derived
: public OtherBase
, public Foo
, public Bar // oops.
{
};
int main() {
Derived d;
OtherBase *pO = &d;
// cross-casting
if (Base *pBase = dynamic_cast<Base *>(pO))
pBase->print();
else
std::cout << "fail" << std::endl;
}
编辑:以免您必须运行此代码...
使用虚拟继承,结果是好的或编译器错误。如果没有虚拟继承,结果要么是好的,要么是无法解释的,难以调试的运行时失败。
当我实现Bar,它基本上复制了Foo已经在做的事情时,它导致动态转换失败,这意味着真实代码中的坏事。
起初我很惊讶没有编译器错误。然后我意识到没有虚拟继承,这会在GCC中触发“没有唯一的最终覆盖”错误。我故意选择不使用虚拟继承,因为在这个设计中不应该有任何钻石。
但是如果我从Base派生时使用了虚拟继承,那么代码也可以正常工作(没有我的oops),我会在编译时被警告过钻石而不是在运行时追踪错误时间。
所以问题是 - 您认为使用虚拟继承来防止将来犯同样的错误是可以接受的吗?在这里使用虚拟继承没有很好的技术原因(我可以看到),因为设计中永远不应该有钻石。它只会强制执行该设计约束。
答案 0 :(得分:2)
不是个好主意。
虚拟继承仅在事先计划时使用。正如您刚刚发现的那样,所有后代类在很多情况下都必须知道它。如果基类具有非默认构造函数,则必须担心它总是由叶类构造。
哦,除非事情发生了变化,否则在没有基类帮助的情况下,你不能将虚拟基类转发给任何派生类。
答案 1 :(得分:2)
这里没有钻石!
你创建的是一个多重继承。每个Base类都有自己的Base副本。
pO有一种OtherBase * 无法将OtherBase *的对象转换为Base *类型 因此动态强制转换将返回NULL指针。
问题是运行时的动态强制转换有一个指向Derived对象的指针。但是从这里到Base是一个暧昧的操作,因此失败了。没有编译器错误,因为dynamic_cast是运行时操作。 (您可以尝试从任何内容转换为任何内容,结果在失败时为NULL(如果使用引用,则抛出)。)
两个选项:
用这个查看:
struct Base
{
Base(int x): val(x) {}
int val;
...
struct Foo : public Base
{
Foo(): Base(1) {}
....
struct Bar : public Base
{
Bar(): Base(2) {}
....
// In main:
std::cout << "Foo" << dynamic_cast<Foo&>(d).val << "\n"
<< "Bar" << dynamic_cast<Bar&>(d).val << "\n";
> ./a.exe
fail
Foo1
Bar2
编译时间检查:
std::cout << static_cast<Base*>(pO) << "\n"; // Should fail to compile.
std::cout << static_cast<Base*>(&d) << "\n"; // Will only fail if ambigious.
// So Fails if Foo and Bar derived from Base
// But works if only one is derived.
答案 2 :(得分:1)
你应该首先考虑的是inheritance is not for code reuse,所以当从两个基础继承共同的祖先和双方实施的方法时,请认为它是两次。
如果您认为您确实想要继承两个基础,那么您将需要使用虚拟继承而不是复制祖先。这在实施exception hierarchies时很常见。请注意,虚拟基础由最派生类型的构造函数直接初始化,需要关注它。