题目
编写程序,剖析多重继承中的经典“死神之菱形”(Diamond Problem):顶层公共基类 Device,被 Printer 和 Scanner 分别继承,底层类 Copier(复印机)同时多重继承两者。演示若不采用虚继承,Copier 内部将包含两份独立的 Device 实例,导致成员访问产生二义性(Ambiguity);随后采用虚继承(virtual public Device),实现顶层基类在派生类内存中仅共享一份唯一实例。
输入格式:无需外部输入。
输出格式:输出虚基类唯一实例的属性访问与内存地址验证日志。
数据范围与约定:多重继承、菱形继承二义性与虚基类表指针(vbptr)。
思路与算法
1. 二义性的产生原因
非虚继承下,Copier 同时拥有 Printer::Device 和 Scanner::Device。若调用 copier.deviceId,编译器无法确认究竟是哪一个基类的数据,抛出 request for member is ambiguous 报错。
2. 虚继承的底层实现
派生类在继承时加上 virtual(如 class Printer : virtual public Device)。编译器会在类中引入虚基类指针(vbptr),将公共基类的数据平移到对象内存的最末端由所有派生分支共同共享引用。
3. 最底层派生类的构造责任
虚基类的构造函数由最底层的派生类(Copier)直接负责调用初始化,中间层类(Printer)对虚基类的构造实参将被忽略。
完整程序
#include <iostream>
// 顶层公共基类
class Device {
public:
int deviceId;
explicit Device(int id) : deviceId(id) {
std::cout << " [Device 物理基类构造] 设备 ID: " << deviceId << "\n";
}
};
// 中间层 1:虚继承 Device
class Printer : virtual public Device {
public:
Printer(int id) : Device(id) {
std::cout << " [Printer 构造] 完成\n";
}
};
// 中间层 2:虚继承 Device
class Scanner : virtual public Device {
public:
Scanner(int id) : Device(id) {
std::cout << " [Scanner 构造] 完成\n";
}
};
// 底层复印机:同时多重继承 Printer 与 Scanner
class Copier : public Printer, public Scanner {
public:
// 关键:虚基类 Device 的初始化由最终派生类 Copier 直接负责调用!
Copier(int id) : Device(id), Printer(id), Scanner(id) {
std::cout << " [Copier 多功能一体机构造完成]\n";
}
};
int main() {
std::cout << "=== 实例化 Copier 复印机对象 (观察 Device 是否只构造一次) ===\n";
Copier copier(8899);
std::cout << "\n--- 二义性消除验证 ---\n";
// 没有任何二义性报错,直接访问公共成员 deviceId
std::cout << "直接读取 copier.deviceId: " << copier.deviceId << "\n";
// 验证 Printer 分支与 Scanner 分支引用的 Device 地址完全一致
Printer* pPrinter = &copier;
Scanner* pScanner = &copier;
std::cout << "Printer 分支下的 deviceId: " << pPrinter->deviceId << "\n";
std::cout << "Scanner 分支下的 deviceId: " << pScanner->deviceId << "\n";
std::cout << "结论:虚继承成功消除了数据冗余与二义性!\n";
return 0;
}运行示例
输出:
=== 实例化 Copier 复印机对象 (观察 Device 是否只构造一次) ===
[Device 物理基类构造] 设备 ID: 8899
[Printer 构造] 完成
[Scanner 构造] 完成
[Copier 多功能一体机构造完成]
--- 二义性消除验证 ---
直接读取 copier.deviceId: 8899
Printer 分支下的 deviceId: 8899
Scanner 分支下的 deviceId: 8899
结论:虚继承成功消除了数据冗余与二义性!