虚基类
本节阅读量:
最上一章,讨论多重继承中,我们研究了“菱形继承问题”。在本节中,我们将继续讨论。
注意:本节是一个高级主题,可以跳过或略读。
菱形继承问题
下面是上一课中的示例(使用一些构造函数),说明问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include <iostream>
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: public PoweredDevice
{
public:
Scanner(int scanner, int power)
: PoweredDevice{ power }
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: public PoweredDevice
{
public:
Printer(int printer, int power)
: PoweredDevice{ power }
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: Scanner{ scanner, power }, Printer{ printer, power }
{
}
};
|
尽管您可能希望获得如下所示的继承关系图:
期望的继承情况
但是如果要创建Copier类对象,默认情况下,您将得到PoweredDevice类的两个副本——一个来自Printer,一个来自Scanner。它具有以下结构:
实际的继承情况
我们可以创建一个简短的示例来展示这一点:
1
2
3
4
5
6
|
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}
|
这将产生以下结果:
1
2
3
4
|
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
|
如您所见,PoweredDevice构造了两次。
虽然这通常是需要的,但在其他时候,您可能只希望Scanner和Printer共享PoweredDevice的一个副本。
虚基类
要共享基类,只需在派生类的继承列表中插入“virtual”关键字。这将创建所谓的虚基类,这意味着只继承一次基类对象。基类对象在继承树中的所有对象之间共享,并且它只构造一次。下面是一个示例(为了简单起见,没有构造函数),展示了如何使用virtual关键字创建共享基类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class PoweredDevice
{
};
class Scanner: virtual public PoweredDevice
{
};
class Printer: virtual public PoweredDevice
{
};
class Copier: public Scanner, public Printer
{
};
|
现在,当您创建Copier类对象时,每个Copier只能获得PoweredDevice的一个副本,该副本将由Scanner和Printer共享。
然而,这又导致了一个问题:如果Scanner和Printer共享PoweredDevice基类,谁负责创建它?答案是Copier。Copier构造函数负责创建PoweredDevice。因此,这允许Copier调用非直接父构造函数的一次:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <iostream>
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: virtual public PoweredDevice // 注: PoweredDevice 是虚基类
{
public:
Scanner(int scanner, int power)
: PoweredDevice{ power } // 为了创建Scanner,必须有这一行,但当前情况下会被忽略
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: virtual public PoweredDevice // 注: PoweredDevice 是虚基类
{
public:
Printer(int printer, int power)
: PoweredDevice{ power } // 为了创建Scanner,必须有这一行,但当前情况下会被忽略
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: PoweredDevice{ power }, // PoweredDevice 在这里被构造
Scanner{ scanner, power }, Printer{ printer, power }
{
}
};
|
这一次,再次运行前面的示例:
1
2
3
4
5
6
|
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}
|
生成结果:
1
2
3
|
PoweredDevice: 3
Scanner: 1
Printer: 2
|
如您所见,PoweredDevice仅构造一次。
有一些细节。
首先,对于大多数派生类的构造函数,虚基类总是在非虚基类之前创建,这确保所有基类都是在其派生类之前创建的。
其次,请注意,Scanner和Printer构造函数仍然调用PoweredDevice构造函数。创建Copier实例时,这些构造函数调用被忽略,因为Copier负责创建PoweredDevice,而不是Scanner和Printer。然而,如果我们要创建Scanner或Printer的实例,则将使用这些构造函数调用,并应用常规继承规则。
第三,如果类继承了一个或多个具有虚父类的类,则最底层派生的类负责构造虚基类。在这种情况下,Copier继承Printer和Scanner,两者都具有PoweredDevice虚拟基类。Copier是最底层派生的类,负责创建PoweredDevice。请注意,即使在单个继承的情况下也是如此:如果Copier单独从Printer继承,并且Printer实际上是从PoweredDevice继承的,则Copier仍然负责创建PoweredDevice。
第四,继承虚基类的所有类都将具有虚函数表,因此类的实例将多一个指针。
因为Scanner和Printer实际上是从PoweredDevice派生的,而Copier将仅构造一个PoweredDevice子对象。Scanner和Printer都需要知道如何找到PoweredDevice子对象,以便它们可以访问其成员(因为它们毕竟是从中派生的)。这通常通过一些虚函数表魔术来完成(它本质上存储从每个子类到PoweredDevice子对象的偏移量)。
25.8 对象切片(Object slicing)
下一节