使用虚拟继承来防止意外创建菱形是否可以接受?
这是一些真实代码的简化,当我没有意识到其他人已经实现了 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;
}
编辑:让您不必运行此代码...
- 如果按原样运行,它会打印“失败”(不希望的,难以调试)。
- 如果删除标记为“oops”的行,它将打印“Foo”(所需的行为)。
- 如果您保留“oops”并将两个继承设置为虚拟,则它将无法编译(但至少您知道要修复什么)。
- 如果删除“oops”并使它们虚拟,它将编译并打印“Foo”(所需的行为)。
对于虚拟继承,结果要么是好的,要么是编译器错误。如果没有虚拟继承,结果要么是好的,要么是无法解释的、难以调试的运行时故障。
当我实现 Bar 时,它基本上重复了 Foo 已经在做的事情,它导致动态转换失败,这意味着实际代码中出现了不好的情况。
起初我很惊讶没有编译器错误。然后我意识到没有虚拟继承,这会触发 GCC 中的“没有唯一的最终重写器”错误。我故意选择不使用虚拟继承,因为这个设计中不应该有任何菱形。
但是,如果我在从 Base 派生时使用虚拟继承,代码也会同样有效(没有我的哎呀),并且我会在编译时收到关于钻石的警告,而不是必须在运行时追踪错误。
所以问题是——你认为使用虚拟继承来防止将来再犯类似的错误可以接受吗?在这里使用虚拟继承没有很好的技术原因(据我所知),因为设计中永远不应该有钻石。它只是为了强制执行该设计约束。
This is a simplification of some real code, and a real mistake I made when I didn't realize someone else had already implemented Foo and derived from it.
#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;
}
EDIT: to save you from having to run this code...
- If run as-is, it prints "fail" (undesireable, hard to debug).
- If you delete the line marked "oops" it prints "Foo" (desired behavior).
- If you leave the "oops" and make the two inheritances virtual, it won't compile (but at least you know what to fix).
- If you delete the "oops" and make them virtual, it will compile and will print "Foo" (desired behavior).
With virtual inheritance, the outcomes are either good or compiler-error. Without virtual inheritance, the outcomes are either good or unexplained, hard-to-debug runtime failure.
When I implemented Bar, which basically duplicated what Foo was already doing, it caused the dynamic cast to fail, which meant bad things in the real code.
At first I was surprised there was no compiler error. Then I realized there was no virtual inheritance, which would have triggered the 'no unique final overrider' error in GCC. I purposefully chose not to use virtual inheritance since there aren't supposed to be any diamonds in this design.
But had I used virtual inheritance when deriving from Base, the code would have worked just as well (without my oops), and I would have been warned about the diamond at compile time vs. having to track down the bug at run time.
So the question is -- do you think it's acceptable to use virtual inheritance to prevent making a similar mistake in the future? There's no good technical reason (that I can see) for using virtual inheritance here, since there should never be a diamond in the design. It would only be there to enforce that design constraint.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这不是一个好主意。
虚拟继承只有在事先计划好的情况下才可以使用。正如您刚刚发现的,在许多情况下,所有后代类都必须知道它。如果基类有一个非默认构造函数,您就必须担心它总是由叶类构造的。
哦,除非自从我上次查看以来情况发生了变化,否则在没有基类帮助的情况下,您无法将虚拟基类向下转换为任何派生类。
Not a good idea.
Virtual inheritance is only to be used when it is planned in advance. As you just discovered, all descendant classes must know about it in many cases. If the base class has a non-default constructor you have to worry about the fact that it is always constructed by the leaf class.
Oh, and unless things have changed since last I looked, you cannot downcast a virtual base class to any derived class without help from the base class.
这里没有钻石!
您创建的是多重继承。每个 Base 类都有自己的 Base 副本。
pO 具有 OtherBase* 类型。
无法将 OtherBase* 的对象转换为 Base* 类型。
因此动态转换将返回 NULL 指针。
问题在于运行时的动态转换有一个指向 Derived 对象的指针。但从这里到达 Base 是一个不明确的操作,因此会失败并返回 NULL。没有编译器错误,因为dynamic_cast是运行时操作。 (您可以尝试从任何内容转换为任何内容,失败时结果为 NULL(如果使用引用,则抛出异常))。
两个选项:
用这个检查一下:
编译时间检查:
There is no diamond here!
What you created was a multiple inheritance. Each Base class has its own copy of Base.
pO has a type of OtherBase*.
There is no way to convert an object of OtherBase* to type Base*.
Thus dynamic cast will return a NULL pointer.
The problem is that dynamic cast at runtime has a pointer to an object of Derived. But to get from here to Base is an ambigious operation and thus fails with a NULL. No compiler error because dynamic_cast is a runtime operation. (You can try to cast from anything to anything the result is a NULL on failure (or throw if using references)).
Two options:
Check it out with this:
Compile Time Check:
您应该考虑的第一件事是 继承不是为了代码重用,因此在从具有共同祖先的两个基类继承并且双方都实现了方法时要三思而后行。
如果您认为您确实想从两个基础继承,那么您将需要使用虚拟继承而不是重复祖先。在实现异常层次结构时,这种情况很常见。请注意,虚拟基是由最派生类型的构造函数直接初始化的,因此需要关心这一点。
First thing you should take into account is that inheritance is not for code reuse, so think it twice when inheriting from two bases with a common ancestor and methods implemented in both sides.
If you think you really want to inherit form both bases anyway, you will want to use virtual inheritance and not duplicate the ancestor. This is common when implementing exception hierarchies. Note that virtual bases are initialized directly by the constructor of the most-derived-type, and needs to care about that.