我想知道什么是“虚拟基类”以及它的含义。

让我举个例子:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

当前回答

虚拟类与虚拟继承不同。虚拟类你不能实例化,虚拟继承完全是另一回事。

维基百科描述得比我好。http://en.wikipedia.org/wiki/Virtual_inheritance

其他回答

虚基类是这样的类 不能被实例化:你不能 从它创建直接对象。

我认为你混淆了两件完全不同的事情。虚拟继承与抽象类不是一回事。虚继承修改函数调用的行为;有时它会解析否则会有歧义的函数调用,有时它将函数调用处理推迟到非虚继承中期望的类。

关于内存布局

作为旁注,可怕的钻石的问题是基类出现了多次。通过常规遗传,你相信你有:

  A
 / \
B   C
 \ /
  D

但是在内存布局中,你有:

A   A
|   |
B   C
 \ /
  D

这解释了为什么当调用D::foo()时,你有一个歧义问题。但真正的问题是当你想要使用a的成员变量时。例如,假设我们有:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

当你试图从D访问m_iValue时,编译器会抗议,因为在层次结构中,它会看到两个m_iValue,而不是一个。如果你修改了一个,比如B::m_iValue (B的A::m_iValue父元素),C::m_iValue将不会被修改(C的A::m_iValue父元素)。

这就是虚拟继承方便的地方,有了它,你会回到一个真正的菱形布局,不仅只有一个foo()方法,还有一个且只有一个m_iValue。

会出什么问题呢?

想象一下:

A有一些基本特征。 B添加了一些很酷的数据数组(例如) C给它添加了一些很酷的特性,比如观察者模式(例如,在m_iValue上)。 D继承了B和C,因此也继承了A。

对于普通继承,从D修改m_iValue是不明确的,必须解决这个问题。即使它是,在D中有两个m_iValues,所以你最好记住这一点,并同时更新这两个。

使用虚拟继承,从D修改m_iValue是可以的…但是…假设你有d,通过它的C接口,你连接了一个观察者。通过它的B接口,你更新了酷数组,这有直接改变m_iValue的副作用…

由于m_iValue的改变是直接完成的(不使用虚拟访问方法),通过C“监听”的观察者将不会被调用,因为实现监听的代码是在C中,而B不知道它…

结论

如果你的层次结构中有一颗钻石,这意味着你有95%的概率在该层次结构中做了错事。

我想对OJ的善意澄清补充一点。

虚拟继承是有代价的。就像所有虚拟的东西一样,你的性能会受到影响。有一种方法可以绕过这种性能冲击,但可能不那么优雅。

你可以在菱形中添加另一层,而不是通过虚拟派生来打破菱形,以得到如下内容:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

没有一个类是虚继承的,所有类都是公开继承的。类D21和D22将隐藏虚函数f(),这对于DD来说是模糊的,可能是通过将函数声明为private。它们将分别定义一个包装器函数f1()和f2(),分别调用class-local (private) f(),从而解决冲突。类DD在需要D11::f()时调用f1(),在需要D12::f()时调用f2()。如果内联定义包装器,可能会得到零开销。

当然,如果您可以更改D11和D12,那么您可以在这些类中执行相同的技巧,但通常情况下并非如此。

虚拟基类用于虚拟继承,是一种使用多重继承时防止在继承层次结构中出现给定类的多个“实例”的方法。

考虑以下场景:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

上面的类层次结构导致了“可怕的钻石”,看起来像这样:

  A
 / \
B   C
 \ /
  D

D的一个实例将由B和C组成,B包含A, C也包含A。所以你有两个A的“实例”(为了更好的表达)。

在这种情况下,就有可能出现模棱两可的情况。当你这样做时会发生什么:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

虚拟继承可以解决这个问题。当您在继承类时指定virtual时,您是在告诉编译器您只需要一个实例。

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

这意味着在层次结构中只包含A的一个“实例”。因此

D d;
d.Foo(); // no longer ambiguous

这是一个小总结。要了解更多信息,请阅读这个和这个。这里还有一个很好的例子。

除了前面提到的多重继承和虚拟继承之外,在Dr Dobb's Journal上还有一篇非常有趣的文章:多重继承被认为是有用的