在上一章 24.9 - 多重继承一课中,我们谈论了菱形继承问题。本章我们会继续该话题。
注意:本节是高级主题,可以作为选修。
菱形继承问题
下面是我们上一课中的例子(多了一些构造函数),说明菱形继承问题:
#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。它有以下结构:
我们可以创建一个简短的示例来演示这一点:
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2
看到了吗?PoweredDevice 被构建了两次。
虽然这通常是期望的结果,但在有时你可能只想让 Scanner 和 Printer 共享 PoweredDevice 的一个副本。
虚基类
要共享基类,只需在派生类的继承列表中插入” virtual “关键字。这将创建所谓的虚基类,这意味着只有一个基类对象。基类对象在继承树中的所有对象之间共享,并且只构造一次。下面是一个示例(为了简单起见,没有构造函数),演示如何使用 virtual 关键字创建共享基类:
class PoweredDevice
{
};
class Scanner: virtual public PoweredDevice
{
};
class Printer: virtual public PoweredDevice
{
};
class Copier: public Scanner, public Printer
{
};现在,当你创建一个 Copier 类对象时,每个Copier将只获得一个由Scanner和Printer共享的PoweredDevice副本。
但是,这又导致了另一个问题:如果Scanner和Printer共享一个PoweredDevice基类,那么谁负责创建它呢?事实证明,答案是 Copier 。 Copier 的构造函数负责创建 PoweredDevice。因此, Copier 可以直接调用非直接父类(non-immediate-parent构造函数一次:
#include <iostream>
class PoweredDevice
{
public:
PoweredDevice(int power)
{
std::cout << "PoweredDevice: " << power << '\n';
}
};
class Scanner: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Scanner(int scanner, int power)
: PoweredDevice{ power } // this line is required to create Scanner objects, but ignored in this case
{
std::cout << "Scanner: " << scanner << '\n';
}
};
class Printer: virtual public PoweredDevice // note: PoweredDevice is now a virtual base class
{
public:
Printer(int printer, int power)
: PoweredDevice{ power } // this line is required to create Printer objects, but ignored in this case
{
std::cout << "Printer: " << printer << '\n';
}
};
class Copier: public Scanner, public Printer
{
public:
Copier(int scanner, int printer, int power)
: PoweredDevice{ power }, // PoweredDevice is constructed here
Scanner{ scanner, power }, Printer{ printer, power }
{
}
};再看前面的例子:
int main()
{
Copier copier{ 1, 2, 3 };
return 0;
}结果:
PoweredDevice: 3
Scanner: 1
Printer: 2
如您所见,PoweredDevice 只构造一次。
有几个容易忽视的细节:
- 首先,虚基类总是在非虚基类之前创建,这确保了所有基类都在它们的派生类之前创建。
- 其次,请注意
Scanner和Printer构造函数仍然有对PoweredDevice构造函数的调用。在创建Copier的实例时,这些构造函数调用将被忽略,因为Copier负责创建PoweredDevice,而不是Scanner或Printer。但是,如果我们要创建Scanner或Printer的实例,就会使用这些构造函数调用,并应用正常的继承规则。
第三,如果一个类继承了一个或多个具有虚父类的类,则最后被派生的类会负责构造虚基类。在本例中,Copier 继承了Printer和Scanner,它们都有一个PoweredDevice虚基类。最最后被派生的类,负责PoweredDevice的创建。注意,即使在单一继承的情况下也是这样:如果Copier从Printer单独继承,而Printer从PoweredDevice虚继承,那么Copier仍然负责创建PoweredDevice。
第四,所有继承虚基类的类都将有一个虚表,即使它们通常没有虚表,因此类的实例其大小会增加一个指针。
因为Scanner和Printer实际上是从PoweredDevice派生的,所以Copier只是一个PoweredDevice子对象(subobject)。Scanner和Printer都需要知道如何找到单个PoweredDevice子对象,这样它们才能访问它的成员(因为它们毕竟是从它派生的)。这通常是通过一些虚表操作来完成的(它实际上存储了从每个子类到PoweredDevice子对象的偏移量)。