#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 回答
使用虚拟基础解释多重继承需要了解C对象模型 . 并且清楚地解释该主题最好在文章而不是评论框中完成 .
我发现最好的,可读的解释解决了我对这个问题的所有怀疑是这篇文章:http://www.phpcompiler.org/articles/virtualinheritance.html
在阅读之后,您真的不需要阅读有关该主题的任何其他内容(除非您是编译器编写者)
Diamond inheritance runnable usage example
此示例显示如何在典型方案中使用虚拟基类:解决钻石继承 .
虚拟类与虚拟继承相同_230755 . 您无法实例化的虚拟类,虚拟继承完全是另一回事 .
维基百科比我更好地描述它 . http://en.wikipedia.org/wiki/Virtual_inheritance
你有点困惑 . 我不知道你是否在混淆一些概念 .
您的OP中没有虚拟基类 . 你只有一个基类 .
你做了虚拟继承 . 这通常用于多继承,以便多个派生类使用基类的成员而不再生成它们 .
不实例化具有纯虚函数的基类 . 这需要Paul得到的语法 . 通常使用它,以便派生类必须定义这些函数 .
我不想再解释这个问题了,因为我没有完全理解你的要求 .
这意味着对虚拟函数的调用将被转发到“正确”类 .
C FAQ Lite FTW .
简而言之,它通常用于多继承场景,其中形成了"diamond"层次结构 . 当您在该类中调用函数并且该函数需要被解析为该底层类之上的类D1或D2时,虚拟继承将打破底层中创建的歧义 . 有关图表和详细信息,请参阅FAQ item .
它也用于姐妹代表团,这是一个强大的功能(虽然不适合胆小的人) . 请参阅this常见问题解答 .
另见有效C第3版(第2版中的43)中的第40项 .
关于内存布局
作为旁注,Dreaded Diamond的问题是基类存在多次 . 因此,通过常规继承,您相信您拥有:
但是在内存布局中,你有:
这解释了为什么在调用
D::foo()
时,你有一个歧义问题 . 但是当你想使用A
的成员变量时会出现 real 问题 . 例如,假设我们有:当你试图从
D
访问m_iValue
时,编译器会抗议,因为在层次结构中,它会看到两个m_iValue
,而不是一个 . 如果您修改一个,比如B::m_iValue
(即B
的A::m_iValue
的父级),则不会修改C::m_iValue
(即C
的A::m_iValue
父级) .这就是虚拟继承的便利之处,就像它一样,你将回到真正的钻石布局,不仅有一个
foo()
方法,而且只有一个m_iValue
.会出什么问题?
想像:
A
有一些基本功能 .B
为它添加了一些很酷的数据(例如)C
为它添加了一些很酷的功能,例如观察者模式(例如,在m_iValue
上) .D
继承自B
和C
,因此来自A
.使用正常继承,从
D
修改m_iValue
是不明确的,必须解决此问题 . 即使它是,m_iValues
里面有两个m_iValues
,所以你最好记住它并同时更新这两个 .使用虚拟继承,从
D
修改m_iValue
是可以的......但是......假设你有D
. 通过它的C
接口,您附加了一个观察者 . 通过它的B
界面,你可以更新酷阵列,它具有直接改变m_iValue
的副作用......由于
m_iValue
的更改是直接完成的(不使用虚拟访问器方法),观察者"listening"到C
将不会被调用,因为实现侦听的代码在C
中,B
不知道它...结论
如果您的层次结构中有钻石,则表示您有95%的人对所述层次结构做错了 .
我认为你混淆了两件截然不同的事情 . 虚拟继承与抽象类不同 . 虚拟继承修改函数调用的行为;有时它会解析函数调用,否则这些函数调用将是模糊的,有时它会将函数调用处理推迟到非虚拟继承中所期望的类 .
我想补充OJ的善意澄清 .
虚拟继承不是没有代价的 . 喜欢所有事物都是虚拟的,你会受到性能的影响 . 这种性能打击有一种方法可能不那么优雅 .
而不是通过虚拟派生来破坏钻石,你可以在钻石上添加另一层,得到这样的东西:
这些类都没有虚拟继承,都是公开继承的 . 然后,类D21和D22将隐藏对DD不明确的虚函数f(),可能通过将该函数声明为私有 . 它们分别定义了一个包装函数f1()和f2(),每个函数调用class-local(private)f(),从而解决冲突 . DD类如果需要D11 :: f()则调用f1(),如果需要D12 :: f()则调用f2() . 如果你内联定义包装器,你可能会得到零开销 .
当然,如果您可以更改D11和D12,那么您可以在这些类中执行相同的操作,但通常情况并非如此 .
除了关于多重和虚拟继承的内容之外,还有一篇关于Dobb博士期刊的非常有趣的文章:Multiple Inheritance Considered Useful
虚拟继承中使用的虚拟基类是一种在使用多重继承时防止出现在继承层次结构中的给定类的多个“实例”的方法 .
请考虑以下情形:
上面的类层次结构导致“可怕的钻石”,如下所示:
D的实例将由B组成,其中包括A,而C也包括A.所以你有两个“实例”(为了更好的表达)A .
当您有这种情况时,您可能会有歧义 . 执行此操作时会发生什么:
虚拟继承可以解决这个问题 . 当您在继承类时指定virtual时,您告诉编译器您只需要一个实例 .
这意味着层次结构中只包含一个A“实例” . 于是
希望有助于作为迷你总结 . 有关更多信息,请阅读this和this . 一个很好的例子也可以here .