在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_iValue
的A::m_iValue
父类),则不会修改C::m_iValue
(即C
的A::m_iValue
父类)。
这就是虚拟inheritance方便的地方,就像它一样,你会回到一个真正的菱形布局,不仅有一个foo()
方法,而且还有一个m_iValue
。
有什么可能出错?
想像:
-
A
有一些基本function。 -
B
给它增加了一些很酷的数据(例如) -
C
增加了一些很酷的function,如观察者模式(例如,在m_iValue
)。 -
D
从B
和C
inheritance,因此从A
inheritance。
在正常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 ++标准)是分开实现的。