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

时间:2020-03-05 18:41:51  来源:igfitidea点击:

我想知道什么是"虚拟基类"及其含义。

让我举一个例子:

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

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

解决方案

回答

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

C ++常见问题精简版FTW。

简而言之,它通常用于形成"钻石"层次结构的多继承方案中。当我们在该类中调用函数时,虚拟继承将打破在底层类中创建的歧义,并且需要将该函数解析为该底层类之上的D1或者D2类。有关图表和详细信息,请参见FAQ项目。

它在姐妹代表团中也使用,这是一个强大的功能(尽管不是胆小的人)。请参阅此常见问题解答。

另请参见有效C ++第三版(第二版43)中的第40项。

回答

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

请考虑以下情形:

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() ??

虚拟继承可以解决这个问题。在继承类时指定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

希望能对我们有所帮助。有关更多信息,请阅读此内容。这里也有一个很好的例子。

回答

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

我认为我们混淆了两个截然不同的事物。虚拟继承与抽象类不是一回事。虚拟继承修改了函数调用的行为。有时,它解决了函数调用,否则该函数调用将是模棱两可的;有时,它将函数调用处理推迟到了非虚拟继承中所期望的类之外的其他类。

回答

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

OP中没有虚拟基类。我们只有一个基类。

我们进行了虚拟继承。通常在多重继承中使用它,以便多个派生类使用基类的成员而不重现它们。

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

我不想对此进行任何解释,因为我无法完全理解要求。

回答

虚拟类与虚拟继承不同。我们无法实例化的虚拟类,虚拟继承完全是另外一回事。

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

回答

我想补充一下OJ的种类说明。

虚拟继承并非没有代价。就像虚拟的所有事物一样,我们会受到性能的影响。解决这种性能问题的方法可能不太优雅。

我们可以通过在钻石上添加另一层来获得类似于以下内容的内容,而不是通过虚拟派生来破坏钻石:

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

没有一个类是虚拟继承的,所有类都是公开继承的。然后,类D21和D22将隐藏对于DD不明确的虚函数f(),可能是通过将函数声明为私有的。他们分别定义了一个包装函数f1()和f2(),每个函数都调用类本地(私有)f(),从而解决了冲突。如果DD类需要D11 :: f(),则调用f1();如果需要D12 :: f(),则调用f2()。如果我们内联定义包装器,则可能会获得大约零开销。

当然,如果我们可以更改D11和D12,则可以在这些类中执行相同的操作,但通常并非如此。

回答

关于内存布局

附带说明一下,Dreaded Diamond的问题在于基类存在多次。因此,通过常规继承,我们相信自己具有:

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"(即" A :::")。 m_iValue`是C的父级)。

这就是使用虚拟继承的方便之处,就像它一样,我们将返回到真正的菱形布局,不仅使用一个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%的人对上述层次结构做错了事。

回答

除了已经说过的关于多重继承和虚拟继承的内容外,Dobb博士的日记中有一篇非常有趣的文章:多重继承被认为是有用的

回答

用虚拟基础解释多重继承需要了解C ++对象模型。最好在文章中而不是在评论框中清楚地说明主题。

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

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