C++基础教程面向对象(学习笔记(60))

虚拟基类

在前面多重继承中,我们没有谈论“钻石问题”。在本节中,我们将继续讨论。

注意:此部分是高级主题,如果需要,可以跳过或略过。

钻石问题

以下是上一课(有一些构造函数)说明钻石问题的示例:

class PoweredDevice
{
public:
    PoweredDevice(int power)
    {
		cout << "PoweredDevice: " << power << '\n';
    }
};
 
class Scanner: public PoweredDevice
{
public:
    Scanner(int scanner, int power)
        : PoweredDevice(power)
    {
		cout << "Scanner: " << scanner << '\n';
    }
};
 
class Printer: public PoweredDevice
{
public:
    Printer(int printer, int power)
        : PoweredDevice(power)
    {
		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。这具有以下结构:
在这里插入图片描述

我们可以创建一个简短的示例来说明这一点:

int main()
{
    Copier copier(1, 2, 3);
}

这会产生结果:

PoweredDevice:3
Scanner:1
PoweredDevice:3
Printer:2
如您所见,PoweredDevice构建了两次。

虽然这通常是需要的,但有时您可能只希望扫描仪和打印机共享一份PoweredDevice副本。

虚拟基类

要共享基类,只需在派生类的继承列表中插入“virtual”关键字即可。这创建了所谓的虚拟基类,这意味着只有一个共享的基础对象。下面是一个示例(为简单起见,没有构造函数),展示了如何使用virtual关键字创建共享基类:

class PoweredDevice
{
};
 
class Scanner: virtual public PoweredDevice
{
};
 
class Printer: virtual public PoweredDevice
{
};
 
class Copier: public Scanner, public Printer
{
};

现在,当您创建一个Copier类时,您将只获得一个将由Scanner和Printer共享的PoweredDevice副本。

但是,这会导致另一个问题:如果扫描仪和打印机共享一个PoweredDevice基类,谁负责创建它?事实证明,答案是复印机。Copier构造函数负责创建PoweredDevice。因此,这是允许Copier直接调用非立​​即父构造函数的一次:

#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) // 此行是创建Printer对象所必需的,但在这种情况下会被忽略
    {
		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)
    {
    }
};

这一次,我们之前的例子:

int main()
{
    Copier copier(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。

第四,虚拟基类始终被认为是其最派生类的直接基础(这就是为什么最派生类负责其构造)。但是继承虚拟基础的类仍然需要访问它。因此,为了促进这一点,编译器为每个直接继承虚拟类(Printer和Scanner)的类创建一个虚拟表。这些虚拟表指向派生类最多的函数。因为派生类具有虚拟表,这也意味着它们现在通过指针(到虚拟表)变大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值