当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《在C++中,什么是虚拟基类?》经验,为你挑选了5个好方法。

我想知道" 虚拟基类 "是什么以及它意味着什么.

让我举个例子:

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

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

OJ... 521

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

请考虑以下情形:

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组成,其中包括A,而C也包括A.所以你有两个"实例"(为了更好的表达)A.

当您有这种情况时,您可能会有歧义.执行此操作时会发生什么:

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

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

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

希望有助于作为迷你总结.有关更多信息,请阅读本文和此内容.这里也有一个很好的例子.



1> OJ...:

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

请考虑以下情形:

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组成,其中包括A,而C也包括A.所以你有两个"实例"(为了更好的表达)A.

当您有这种情况时,您可能会有歧义.执行此操作时会发生什么:

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

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

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

希望有助于作为迷你总结.有关更多信息,请阅读本文和此内容.这里也有一个很好的例子.


你的"可怕的钻石"图表令人困惑,虽然它似乎是常用的.这实际上是一个显示类继承关系的图表 - *不是*对象布局.令人困惑的部分是,如果我们使用`virtual`,那么对象布局看起来像钻石; 如果我们不使用`virtual`,那么对象布局看起来就像一个包含两个`A`s的树结构
@Bohdan使用虚拟关键字的次数更少,因为当我们使用虚拟关键字时,会应用重量级的机制.因此,您的计划效率将会降低.
@Bohdan没有它不:)
@OJ.为什么不?他们很搞笑:)
由于MM概述的原因,我不得不对这个答案进行投票 - 该图表示了与帖子相反的结果.
但严重的是,当我们不使用虚拟时会出现什么情况?
@OJ.只是想知道一件事,在使用虚拟关键字之后,然后调用哪个实例就像`d.Foo()//这是B的Foo()或C的Foo()(在使用虚拟关键字之后)
@Viktor,它并不重要,它不属于`B`或`C`.只是认为它是一个合并的方法实际上属于'祖父A`
@lavsprat你做`dB :: foo()`
如果通过键入`d.Foo()`我想从`B`类调用`Foo()`怎么办?
很棒的答案。对我而言,一个尚未解决的问题是,在使用虚拟继承时将使用B或C的A版本中的哪一个?我们能知道吗?也是模棱两可的,是否取决于某件事,是否无关紧要?提前致谢。
这似乎也造成了另一个问题......如果Foo是虚拟的,B和C有不同的Foo实现呢?当它打电话给Foo时D应该做什么?是否有一个实际的用例,使A继承实际上解决了什么?我的意思是必须有这个被采纳到标准中,对吗?

2> paercebal..:

关于内存布局

作为旁注,Dreaded Diamond的问题在于基类存在多次.因此,通过常规继承,您相信您拥有:

  A
 / \
B   C
 \ /
  D

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

A   A
|   |
B   C
 \ /
  D

这解释了为什么在通话时D::foo(),你有一个模棱两可的问题.但是当你想要使用一个成员变量时,真正的问题出现了A.例如,假设我们有:

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

当你试图访问m_iValueD,编译器会抗议,因为在层次结构中,它会看到两个m_iValue,而不是一个.如果你修改一个,比如说,B::m_iValue(也就是其中的A::m_iValue父代B),C::m_iValue将不会被修改(即它的A::m_iValue父代C).

这就是虚拟继承的便利之处,就像它一样,你将回到真正的钻石布局,不仅有一种foo()方法,而且只有一种方法m_iValue.

怎么可能出错?

想像:

A 有一些基本功能.

B 添加了一些很酷的数据(例如)

C添加一些很酷的功能,如观察者模式(例如,打开m_iValue).

D继承自BC,从而继承自A.

与正常的继承,修改m_iValueD是模糊的,这必须得到解决.即使它是,m_iValues内部有两个D,所以你最好记住并同时更新这两个.

随着虚拟继承,修改m_iValueD是好的......但是......比方说,你有D.通过它的C界面,你附上了一个观察者.通过它的B界面,你可以更新酷阵列,它具有直接改变的副作用m_iValue......

由于变化m_iValue是直接完成(不使用虚拟存取方法),观察者"听",通过C将不会被调用,因为实施监听的代码是C,和B不知道它...

结论

如果您的层次结构中有钻石,则表示您有95%的人对所述层次结构做错了.


@Chris Dodd:不完全是.使用m_iValue会发生什么符号(*例如typedef,成员变量,成员函数,强制转换为基类等*).这确实是一个多重继承问题,用户应该知道正确使用多重继承的问题,而不是采用Java方式并得出结论"多重继承是100%邪恶,让我们用接口做".
FWIW,万一有人想知道,成员变量*不能虚拟 - 虚拟是*函数*的说明符.参考:http://stackoverflow.com/questions/3698831/can-a-class-have-virtual-data-members

3> lenkite..:

使用虚拟基础解释多重继承需要了解C++对象模型.并且清楚地解释该主题最好在文章中而不是在评论框中完成.

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

在阅读完之后,您真的不需要阅读有关该主题的任何其他内容(除非您是编译器编写者)



4> wilhelmtell..:

虚基类是无法实例化的类:您无法从中创建直接对象.

我认为你混淆了两件截然不同的事情.虚拟继承与抽象类不同.虚拟继承修改函数调用的行为; 有时它会解析函数调用,否则这些函数调用将是模糊的,有时它会将函数调用处理推迟到非虚拟继承中所期望的类.



5> wilhelmtell..:

我想补充OJ的善意澄清.

虚拟继承不是没有代价的.就像所有虚拟事物一样,你会受到性能影响.这种性能打击有一种方法可能不那么优雅.

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

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

这些类都没有虚拟继承,都是公开继承的.然后,类D21和D22将隐藏虚拟函数f(),这对于DD来说是不明确的,可能通过将该函数声明为私有.它们分别定义了一个包装函数f1()和f2(),每个函数调用class-local(private)f(),从而解决冲突.DD类如果需要D11 :: f()则调用f1(),如果需要D12 :: f()则调用f2().如果你内联定义包装器,你可能会得到零开销.

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


这不是或多或少优雅或解决歧义的问题(你总是可以使用显式的xxx ::规范).对于非虚拟继承,类DD的每个实例都有*两个*独立的B实例.只要该类具有单个非静态数据成员,虚拟和非虚拟继承的区别就不仅仅是语法.
推荐阅读
mobiledu2402851373
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有