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

让我举个例子:

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

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

当前回答

关于内存布局

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

  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%的概率在该层次结构中做了错事。

其他回答

这意味着对虚函数的调用将被转发到“正确的”类。

C+ FAQ上升。

简而言之,它通常用于多继承场景,其中形成了“菱形”层次结构。虚继承将打破在底层类中创建的模糊性,当您调用该类中的函数并且函数需要解析到底层类之上的类D1或D2时。有关图表和详细信息,请参阅FAQ项。

它还用于姐妹委托,这是一个强大的功能(尽管不适合心脏薄弱的人)。请看这个FAQ。

参见Effective c++第3版中的第40项(第2版中的43项)。

关于内存布局

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

  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%的概率在该层次结构中做了错事。

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

考虑以下场景:

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

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

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

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

你有点让人困惑了。我不知道你是否混淆了一些概念。

op中没有虚基类,只有基类。

你做了虚拟继承。这通常用于多重继承,以便多个派生类使用基类的成员而不复制它们。

不实例化具有纯虚函数的基类。这需要Paul掌握的语法。通常使用它是为了派生类必须定义这些函数。

我不想再解释了,因为我不太明白你在问什么。