我想知道什么是“虚拟基类”以及它的含义。
让我举个例子:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
我想知道什么是“虚拟基类”以及它的含义。
让我举个例子:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
当前回答
除了前面提到的多重继承和虚拟继承之外,在Dr Dobb's Journal上还有一篇非常有趣的文章:多重继承被认为是有用的
其他回答
这意味着对虚函数的调用将被转发到“正确的”类。
C+ FAQ上升。
简而言之,它通常用于多继承场景,其中形成了“菱形”层次结构。虚继承将打破在底层类中创建的模糊性,当您调用该类中的函数并且函数需要解析到底层类之上的类D1或D2时。有关图表和详细信息,请参阅FAQ项。
它还用于姐妹委托,这是一个强大的功能(尽管不适合心脏薄弱的人)。请看这个FAQ。
参见Effective c++第3版中的第40项(第2版中的43项)。
虚拟基类用于虚拟继承,是一种使用多重继承时防止在继承层次结构中出现给定类的多个“实例”的方法。
考虑以下场景:
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和C组成,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
这是一个小总结。要了解更多信息,请阅读这个和这个。这里还有一个很好的例子。
我想对OJ的善意澄清补充一点。
虚拟继承是有代价的。就像所有虚拟的东西一样,你的性能会受到影响。有一种方法可以绕过这种性能冲击,但可能不那么优雅。
你可以在菱形中添加另一层,而不是通过虚拟派生来打破菱形,以得到如下内容:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
没有一个类是虚继承的,所有类都是公开继承的。类D21和D22将隐藏虚函数f(),这对于DD来说是模糊的,可能是通过将函数声明为private。它们将分别定义一个包装器函数f1()和f2(),分别调用class-local (private) f(),从而解决冲突。类DD在需要D11::f()时调用f1(),在需要D12::f()时调用f2()。如果内联定义包装器,可能会得到零开销。
当然,如果您可以更改D11和D12,那么您可以在这些类中执行相同的技巧,但通常情况下并非如此。
钻石继承可运行的用法示例
这个示例展示了如何在典型场景中使用虚拟基类:解决菱形继承问题。
考虑下面的工作示例:
main.cpp
#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);
}
编译并运行:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
如果我们移除virtual into:
class B : public virtual A
我们会得到一堵关于GCC无法解析D成员和通过a继承两次的方法的错误墙:
main.cpp:27:7: warning: virtual base ‘A’ inaccessible in ‘D’ due to ambiguity [-Wextra]
27 | class D : public B, public C {
| ^
main.cpp: In member function ‘virtual int D::h()’:
main.cpp:30:40: error: request for member ‘i’ is ambiguous
30 | virtual int h() { return this->i + this->j + this->k; }
| ^
main.cpp:7:13: note: candidates are: ‘int A::i’
7 | int i;
| ^
main.cpp:7:13: note: ‘int A::i’
main.cpp: In function ‘int main()’:
main.cpp:34:20: error: invalid cast to abstract class type ‘D’
34 | D d = D(1, 2, 4);
| ^
main.cpp:27:7: note: because the following virtual functions are pure within ‘D’:
27 | class D : public B, public C {
| ^
main.cpp:8:21: note: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:34:7: error: cannot declare variable ‘d’ to be of abstract type ‘D’
34 | D d = D(1, 2, 4);
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:35:14: error: request for member ‘f’ is ambiguous
35 | assert(d.f() == 3);
| ^
main.cpp:8:21: note: candidates are: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:17:21: note: ‘virtual int B::f()’
17 | virtual int f() { return this->i + this->j; }
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:36:14: error: request for member ‘g’ is ambiguous
36 | assert(d.g() == 5);
| ^
main.cpp:9:21: note: candidates are: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:24:21: note: ‘virtual int C::g()’
24 | virtual int g() { return this->i + this->k; }
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
./main.out
在GCC 9.3.0, Ubuntu 20.04上测试。
虚拟类与虚拟继承不同。虚拟类你不能实例化,虚拟继承完全是另一回事。
维基百科描述得比我好。http://en.wikipedia.org/wiki/Virtual_inheritance