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

我想知道什么是“ 虚拟基类 ”,是什么意思。

让我举个例子:

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

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

考虑以下情况:

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

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

  A / \ BC \ / D 

D的一个实例将由B组成,其中包括A和C,其中也包括A.所以你有两个“实例”(为了更好的expression)。

当你有这种情况下,你有可能模糊。 当你这样做时会发生什么:

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

虚拟inheritance就是为了解决这个问题。 当你inheritance你的类时指定虚拟的,你告诉编译器你只需要一个实例。

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

这意味着层次结构中只包含一个“实例”。 于是

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

希望有助于作为一个小的总结。 欲了解更多信息,请阅读这个和这个 。 这里也有一个很好的例子。

关于内存布局

作为一个方面说明,Dreaded Diamond的问题是基类多次出现。 所以经常inheritance,你相信你有:

  A / \ BC \ / D 

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

 AA | | BC \ / D 

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

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

当你尝试从D访问m_iValue时,编译器会抗议,因为在层次结构中,它会看到两个m_iValue ,而不是一个。 如果你修改一个B::m_iValue (即B::m_iValueA::m_iValue父类),则不会修改C::m_iValue (即CA::m_iValue父类)。

这就是虚拟inheritance方便的地方,就像它一样,你会回到一个真正的菱形布局,不仅有一个foo()方法,而且还有一个m_iValue

有什么可能出错?

想像:

  • A有一些基本function。
  • B给它增加了一些很酷的数据(例如)
  • C增加了一些很酷的function,如观察者模式(例如,在m_iValue )。
  • DBCinheritance,因此从Ainheritance。

在正常inheritance的情况下,从D修改m_iValue是不明确的,必须解决。 即使是这样, D里面也有两个m_iValues ,所以你最好记住它,并且同时更新它们。

使用虚拟inheritance,从D修改m_iValue是可以的…但是…假设你有D 通过它的C接口,你附加了一个观察者。 通过它的B接口,你更新了酷阵,具有直接改变m_iValue

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

结论

如果您的层次结构中有钻石,则表示您有95%的人在所述层次结构中做了错误的事情。

用虚拟基础解释多重inheritance需要了解C ++对象模型。 明确地解释这个话题最好在一篇文章中完成,而不是在评论框中完成。

我发现最好的,可读的解释解决了这个问题上的所有疑问,这篇文章: http : //www.phpcompiler.org/articles/virtualinheritance.html

你真的不需要阅读关于该主题的任何其他内容(除非你是一个编译器作家)阅读后…

虚拟基类是一个不能被实例化的类:你不能创build直接的对象。

我认为你混淆了两个完全不同的东西。 虚拟inheritance和抽象类不是一回事。 虚拟inheritance修改了函数调用的行为; 有时它解决了函数调用,否则它会是模糊的,有时它将函数调用处理延迟到一个类,而不是在非虚拟inheritance中期望的类。

我想补充OJ善意的澄清。

虚拟inheritance不是没有代价的。 与所有虚拟事物一样,你的performance也会受到影响。 有一种方法可能会降低性能,可能不太优雅。

而不是通过虚拟化来打破钻石,你可以在钻石上添加另外一层,得到这样的东西:

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

没有一个类实际上inheritance,都是公开inheritance的。 然后类D21和D22将隐藏DD的模糊虚函数f(),也许通过声明私有函数。 它们分别定义一个包装函数f1()和f2(),每个函数调用class-local(private)f(),从而解决冲突。 如果DD需要D11 :: f()和f2()(如果需要D12 :: f(),则DD类调用f1()。 如果你定义内联的包装,你可能会得到零开销。

当然,如果你可以改变D11和D12,那么你可以在这些类中做相同的诀窍,但通常情况并非如此。

除了关于多重和虚拟inheritance的说法之外,还有一篇非常有趣的文章,关于Dobb博士的“Journal: Multiple Inheritance Considered Useful

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

C ++ FAQ Lite FTW。

简而言之,它经常用于形成“钻石”层次结构的多inheritance场景。 然后,虚拟inheritance将打破在底层类中创build的歧义,当您调用该类中的函数时,需要将该函数parsing为高于该底层类的D1或D2级。 查看常见问题解答项目的图表和细节。

它也用于姊妹代表团 ,这是一个强大的function(尽pipe不适合昏迷)。 看这个常见问题。

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

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

您的OP中没有虚拟基类。 你只有一个基类。

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

具有纯虚函数的基类不会被实例化。 这需要Paul得到的语法。 它通常用于派生类必须定义这些函数。

我不想再解释这个,因为我不完全明白你在问什么。

Diamondinheritance可运行的使用示例

这个例子展示了如何在典型场景中使用虚拟基类:解决钻石inheritance。

 #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(df() == 3); assert(dg() == 5); assert(dh() == 7); } 

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

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

这是我的面试问题之一。 几乎所有人都认为“虚拟基类”是指“纯虚指标类”,就像这里的一个答案。 这是不正确的。 在OOdevise中,您经常会看到具有多个接口的具体类。 有一个叫做“基于策略的实现”的概念,它实现了一些或全部的接口,你可以插入一个实现并重用。 在面向对象编程的背景下,您没有听说过基于策略的实现,因为Java和C#不支持它。 但是Python和C ++实际上是这样做的。

它看起来像这个接口:

 struct ICanFly { virtual ~ICanFly() = default; virtual void fly() = 0; }; struct IStrong { virtual ~IStrong() = default; virtual void powerful() =0 ; }; struct IUncanny { virtual ~IUncanny() = default; virtual void predict() = 0; }; struct ISuperHero :virtual ICanFly ,virtual IStrong , virtual IUncanny {}; 

库存实现:

 struct Bird: virtual ICanFly { void fly()override { flapwings(); } }; struct Plane :virtual ICanFly { void fly()override { movefast(); } }; struct Rocket :virtual ICanFly { void fly()override { generatethrust(); } }; struct Locomotive :virtual IStrong { void powerful()override { chug(); } }; struct BigDam :virtual IStrong { void powerful()override { holdback(); } }; struct Psychic :virtual IUncanny { void predict()override { useintuition(); } }; struct AI :virtual IUncanny { void predict()override { compute(); } }; 

还有一些实现重用库存类

 struct SuperMan final :protected AI, protected Rocket, protected BigDam, ISuperHero {}; struct WonderWoman final : protected Psychic , protected Plane, ISuperHero { void powerful()final { lasso(); } }; 

这里保护的使用抑制了对实现类的隐式转换,也就是说,实现不会build模“is-a”关系。 只有接口这样做。 (公共的是最后一个,主要是因为inheritance规则的C ++顺序,最右边的inheritance是“支配”)

接口通常具有多种function,实际上我可以这样做

 struct IFooBar { virtual ~IFooBar() = default; virtual void foo() = 0; virtual void bar() = 0; }; struct Foo1:virtual IFooBar { void foo()override {} }; struct Foo1 :virtual IFooBar { void foo() override {} }; struct Bar1 :virtual IFooBar { void bar() override {} }; struct Bar2 :virtual IFooBar { void bar() override {} }; struct Final11 final :protected Bar1, protected Foo1,virtual IFooBar {}; struct Final21 final :protected Bar2, protected Foo1,virtual IFooBar {}; 

我有很多项目使用这种风格。 这里的“最终”说明符有助于优化器解决它可以安全地虚拟化的地方。 C ++中的Devirtualization,第1部分导致类似于如果一切都是模板的类。

 inline void dofoo(IFooBar const&d) { d.foo(); } template<class X> void dofooT(X const&d) { d.foo(); } Final11 f; dofooT(f);//compiler knows the final type here dofoo(f);//it does here too //no difference IFooBar const& g=dynamic_cast<IFooBar& >(someobject); dofooT(f);//compiler doesn't know the final type here, calls virtual dofoo(f);//it doesnt here either //no difference 

然而,“纯界面”方法确实具有模块化的明显优势,这也是人们使用它的原因。 当然,通过模块边界,编译器没有太多机会去虚拟化。 我做了几次dynamic的“幕后”转换,看是否能够发现经常使用的实现类,从而进行了优化。 这就像一个冠军。

在C ++中,“基于策略的实现”通常是使用模板完成的,而不是与虚函数的接口,模板在系统的各个部分引入了大量的耦合。 微软的ATL是一个很好的例子,它使用虚函数,但不是虚拟的基础,因为COM机制与MSVC编译器中的dynamictypesparsing(实际上早于C ++标准)是分开实现的。