1

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.

4

3 回答 3

2

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.

于 2009-09-09T22:12:59.750 回答
2

这里没有钻石!
您创建的是多重继承。每个 Base 类都有自己的 Base 副本。

pO 的类型为 OtherBase*。
无法将 OtherBase* 的对象转换为 Base* 类型。
因此动态转换将返回一个 NULL 指针。

问题是运行时的动态转换有一个指向 Derived 对象的指针。但是从这里到 Base 是一个模棱两可的操作,因此失败并返回 NULL。没有编译器错误,因为 dynamic_cast 是运行时操作。(您可以尝试从任何东西转换为任何结果,失败时为 NULL(或者如果使用引用则抛出))。

两种选择:

  • 如果你转换引用,你可以让 dynamic_cast 抛出异常。
  • 或者您可以使用在编译时检查的强制转换 static_cast<>

用这个检查一下:

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.
于 2009-09-09T22:25:16.950 回答
1

您应该考虑的第一件事是继承不是为了代码重用,因此在从具有共同祖先的两个基类和双方实现的方法继承时要三思而后行。

如果你认为你真的想从两个基础上继承,你会想要使用虚拟继承而不是复制祖先。这在实现异常层次结构时很常见。请注意,虚拟基由最派生类型的构造函数直接初始化,需要注意这一点。

于 2009-09-10T20:07:09.403 回答