我想知道“ 虚拟基类 ”是什么以及它意味着什么。

让我举个例子:

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

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

===============>>#1 票数:519 已采纳

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

请考虑以下情形:

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

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

===============>>#2 票数:238

关于内存布局

作为旁注,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 (那就是A::m_iValue的父B ), C::m_iValue不会被修改(也就是A::m_iValue的母公司C )。

这就是虚拟继承的便利之处,就像它一样,你将回到真正的钻石布局,不仅有一个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 ,并且B不知道它,因此不会调用通过C “侦听”的观察者。 。

结论

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

===============>>#3 票数:32

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

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

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

===============>>#4 票数:9

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

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

===============>>#5 票数:6

我想补充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,那么您可以在这些类中执行相同的操作,但通常情况并非如此。

===============>>#6 票数:4

除了关于多重和虚拟继承的内容之外,还有一篇关于Dobb博士期刊的非常有趣的文章: 多重继承被认为是有用的

===============>>#7 票数:1

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

C ++ FAQ Lite FTW。

简而言之,它通常用于多继承场景,其中形成“菱形”层次结构。 当您在该类中调用函数并且该函数需要被解析为该底层类之上的类D1或D2时,虚拟继承将打破底层中创建的歧义。 有关图表和详细信息,请参阅FAQ项目

它也用于姐妹代表团 ,这是一个强大的功能(虽然不适合胆小的人)。 请参阅常见问题

另见有效C ++第3版(第2版中的43)中的第40项。

===============>>#8 票数:1

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

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

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

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

我不想再解释这个问题了,因为我没有完全理解你的要求。

===============>>#9 票数:1

钻石继承可运行的用法示例

此示例显示如何在典型方案中使用虚拟基类:解决钻石继承。

#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);
}

===============>>#10 票数:0

虚拟类是一样的虚拟继承。 您无法实例化的虚拟类,虚拟继承完全是另一回事。

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

  ask by popopome translate from so

未解决问题?本站智能推荐:

3回复

C ++中的虚拟基类

我有一个关于虚拟基类的查询。 为了解决多重继承中的“死亡之钻” /歧义性问题,引入了虚拟基类。 class C声明中未使用关键字virtual时会发生什么。 您能详细解释一下吗?
1回复

为什么C ++ 11中的统一初始化在虚拟基类中表现得很奇怪?

现在,我正在学习C ++中的继承功能,并希望测试最近学到的虚拟基类的概念。 我尝试了以下简单的代码: 请注意C类构造函数中的B(p_value1, p_value2) 。这给了我想要的输出: 但是,当我将其更改为B{p_value1, p_value2} ,我得到以下输出:
1回复

C ++ Static_cast指向虚拟基类的指针

大约6 o 7年后几乎完全用C ++编程,我发现: 抛出编译器错误,因为标准不允许从指向虚拟基类的指针进行静态强制转换。 我假设这与与虚拟基类关联的内存布局有关,但我想知道细节。
3回复

基类中的虚拟继承和空vtable

有以下代码: 我已经读到,当虚拟继承某个类时,将为派生类创建空的vtable,因此内存布局如下: 它是12个字节。 问题是-如果没有任何虚拟方法,此空 vtable的目的是什么?如何使用它?
2回复

虚拟基类创建顺序

我有以下问题: 运行此代码时,答案是: A1 A2 A1 AA2 A2 AA1 B. 我想了解第一个A1创建的位置。 我知道在非虚拟类之前调用​​虚拟类的规则,但是第一个A1是困扰我的问题。
3回复

(为什么)在纯虚拟派生类中是否需要虚拟基类构造函数调用?

我有一个带有菱形结构的类层次结构,并且没有默认构造函数的基类,因为它具有引用成员。 该代码如下所示: 编辑 :添加了虚拟基础析构函数(原始代码具有它) 请注意, Left和Right是纯虚类,即它们的构造函数在任何情况下都不会调用Base的构造函数。 这是由于虚拟继承,这意
3回复

混合基类的虚拟和非虚拟继承

这是代码: 此代码打印: 为什么? 由于创建了Centaur对象,因此我们首先通过构建Human , Animal以及Centaur来构建Centaur (我们从派生程度最低的对象到生成程度最高的对象)。 让我们从Human开始: Human继承自Biology ,因
4回复

C ++中的虚拟继承

我在阅读c ++中的虚拟继承时在网站上发现了这个 使用多重继承时,有时需要使用虚拟继承。 一个很好的例子是标准的iostream类层次结构: C ++如何确保只存在虚拟成员的单个实例,而不管从中派生的类的数量是多少? C ++使用额外的间接级别来访问虚拟类,通常是通过指针 。
5回复

C ++中的虚拟继承被误解

我读到在虚拟继承中,构造函数被称为“从最派生的”。 考虑下面的代码。 以我的观点,这里派生最多的类是D。然后是B和C,“派生最多的”是A。那么,为什么首先调用最“ Base”的构造函数而不是“最派生”? 谢谢。 这是输出: 更新: 好。 因此请考虑以下代码。 请
2回复

虚拟基类初始化

我正在测试,我很难理解这一点: 返回的值为20 000,但实际上我不明白为什么: 代表虚拟基类的所有子对象都由最派生类的构造函数初始化。 如果最派生类的构造函数未为虚拟基类V指定mem初始化程序,则将调用V的默认构造函数以初始化虚拟基类子对象。 我尝试了其他方法在派生