在C ++中,什么是虚拟基类?

In C++, what is a virtual base class?

我想知道"虚拟基类"是什么,它意味着什么。

让我举个例子:

1
2
3
4
5
6
7
8
9
10
11
class Foo
{
public:
    void DoSomething() { /* ... */ }
};

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


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

考虑以下情况:

1
2
3
4
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

上面的类层次结构导致"可怕的菱形",它看起来像这样:

1
2
3
4
5
  A
 / \
B   C
 \ /
  D

D的一个实例将由B组成,B包括A,C也包括A。所以您有两个A的"实例"(为了更好的表达)。

当你有这种情况时,你就有可能产生歧义。当你这样做时会发生什么:

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

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

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

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

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

希望这可以作为一个小总结。有关更多信息,请阅读此和此。这里也有一个很好的例子。


关于内存布局

作为旁注,可怕的钻石的问题在于,基类存在多次。所以,有了固定的继承权,你相信你有:

1
2
3
4
5
  A
 / \
B   C
 \ /
  D

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

1
2
3
4
5
A   A
|   |
B   C
 \ /
  D

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

1
2
3
4
5
6
class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

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

这就是虚拟继承的用武之地,因为有了它,您将返回到真正的菱形布局,不仅使用一个foo()方法,而且还使用一个而且只有一个m_iValue方法。

什么会出错?

想象:

  • A具有一些基本特性。
  • B增加了一些很酷的数据数组(例如)
  • C增加了一些很酷的特性,比如观察模式(例如,在m_iValue上)。
  • D继承自BC,因此继承自A

在正常继承情况下,从D修改m_iValue是不明确的,必须加以解决。即使是这样,在D中也有两个m_iValues,所以你最好记住这一点,同时更新这两个。

通过虚拟继承,可以从D修改m_iValue。但是…我们假设你有一个D。通过它的C接口,您附加了一个观察者。通过它的B接口,你可以更新cool数组,它有直接改变m_iValue的副作用。

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

结论

如果你的等级制度中有一颗钻石,这意味着你有95%的人会对所说的等级制度做错事。


用虚拟基础解释多重继承需要一个C++对象模型的知识。在一篇文章中,而不是在评论框中,清楚地解释主题是最好的。

我发现解决我对这个问题的所有疑问的最好、易读的解释就是这篇文章:http://www.phpcompiler.org/articles/virtualInheritance.html

你真的不需要在读了这个主题之后再去读其他任何关于这个主题的东西(除非你是一个编译器编写者)。


A virtual base class is a class that
cannot be instantiated : you cannot
create direct object out of it.

我觉得你把两件截然不同的事情搞混了。虚拟继承与抽象类不同。虚拟继承修改了函数调用的行为;有时它会解析不明确的函数调用,有时它会将函数调用处理延迟到非虚拟继承中预期的类以外的类。


我想补充一下OJ的澄清。

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

你可以在钻石上再加一层,而不是通过虚拟派生来破坏钻石,这样可以得到:

1
2
3
4
5
6
7
   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

没有一个类实际上继承,所有类都公开继承。然后,类d21和d22将隐藏对dd不明确的虚函数f(),可能通过声明函数private来实现。它们分别定义了包装函数f1()和f2(),每个调用类local(private)f(),从而解决了冲突。如果需要d11::f(),则类dd调用f1();如果需要d12::f(),则类dd调用f1()。如果您在内联中定义包装器,您可能会得到大约零的开销。

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


除了已经提到的多重和虚拟继承之外,Dobb博士的期刊上还有一篇非常有趣的文章:多重继承被认为是有用的


Diamond继承可运行用法示例

这个例子展示了如何在典型场景中使用虚拟基类:解决菱形继承问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}


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

您的操作中没有虚拟基类。您只有一个基类。

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

没有实例化具有纯虚拟函数的基类。这需要保罗掌握的语法。它通常用于派生类必须定义这些函数。

我不想再解释了,因为我不完全明白你的要求。


这意味着对虚拟函数的调用将被转发到"right"类。

C++ FAQ LITE FTW。

简而言之,它通常用于多个继承场景中,在这些场景中形成一个"菱形"层次结构。然后,当您调用该类中的函数时,虚拟继承将打破在底层类中创建的模糊性,并且需要将该函数解析为该底层类之上的类D1或D2。有关图表和详细信息,请参阅常见问题解答项。

它也被用于姐妹代表团,一个强大的功能(虽然不是为微弱的心)。请参阅此常见问题解答。

也可以看到有效的C++第三版(第二版中的43)中的第40项。


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

维基百科比我描述得更好。http://en.wikipedia.org/wiki/virtual_继承