虚继承
本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 54 课,共 104 课。
当一个类继承自两个共享同一个基类的类时,就会出现菱形问题。如果不进行特殊处理,派生类最终会包含两份公共基类的副本,导致歧义并浪费内存。
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // 错误:存在歧义,指的是哪个 'age'?Bat 类包含两个独立的 Animal 子对象:一个通过 Mammal,另一个通过 Bird。这形成了菱形继承图,这个问题也因此得名。
虚继承通过确保公共基类只有一份来解决这个问题。从共享基类继承时,添加 virtual 关键字:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // 可以正常运行!只有一个 'age'。请注意,Bat 必须在其构造函数中直接初始化 Animal。使用虚继承时,无论中间类是什么,最派生类都负责构造虚基类。
挑战
简单让我们构建一个员工管理系统,展示虚拟继承如何解决菱形问题。你将创建一个继承层级,其中 TeamLead 同时继承自 Developer 和 Manager,而这两个类都共享同一个基类 Employee。
你需要将代码分为 4 个文件:
Employee.h:定义公共基类Employee,其中包含:- 受保护的
std::string name和int id - 一个接收这两个值并打印以下内容的构造函数:
Employee [<name>] hired with ID <id> - 一个公共
getInfo()方法,打印以下内容:Employee: <name> (ID: <id>) - 一个虚析构函数,打印以下内容:
Employee [<name>] record closed
- 受保护的
Developer.h:定义一个通过虚公共继承自Employee的Developer类:- 一个受保护的
std::string language成员 - 一个接收 name、id 和 language 的构造函数:将 name 和 id 传递给
Employee,保存 language,并打印以下内容:Developer [<name>] specializes in <language> - 一个公共
code()方法,打印以下内容:<name> is coding in <language> - 一个析构函数,打印以下内容:
Developer [<name>] signed off
- 一个受保护的
Manager.h:定义一个通过虚公共继承自Employee的Manager类:- 一个受保护的
int teamSize成员 - 一个接收 name、id 和 team size 的构造函数:将 name 和 id 传递给
Employee,保存 team size,并打印以下内容:Manager [<name>] leads a team of <teamSize> - 一个公共
manage()方法,打印以下内容:<name> is managing <teamSize> people - 一个析构函数,打印以下内容:
Manager [<name>] stepped down
- 一个受保护的
main.cpp:读取 4 个输入(每个输入单独占一行):- 姓名(字符串)
- 员工 ID(整数)
- 编程语言(字符串)
- 团队人数(整数)
定义一个公共继承自
Developer和Manager的TeamLead类:- 一个接收全部 4 个参数的构造函数,必须先直接初始化
Employee(虚基类),然后初始化Developer和Manager - 构造函数应打印:
TeamLead [<name>] ready to lead and code! - 一个按顺序调用
getInfo()、code()和manage()的showRole()方法 - 一个析构函数,打印以下内容:
TeamLead [<name>] promoted out
在一个代码块作用域内创建一个
TeamLead对象,调用showRole(),然后让它离开作用域。代码块结束后,打印:Organization restructured!
例如,输入 Alice、101、C++ 和 5 时:
Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!注意,Employee 构造函数只调用一次,Employee 析构函数也只调用一次:虚拟继承确保共享基类只有一个副本。TeamLead 必须直接初始化 Employee,因为在虚拟继承中,最派生类负责构造虚基类。
亲手试试
#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"
using namespace std;
// TODO: 定义 TeamLead 类,公开继承 Developer 和 Manager
// 记住:使用虚继承时,TeamLead 必须直接初始化 Employee(虚基类)
class TeamLead : public Developer, public Manager {
public:
// TODO: 实现接收 name、id、language 和 teamSize 的构造函数
// 必须先初始化 Employee(虚基类),再初始化 Developer,最后初始化 Manager
// 应输出:TeamLead [<name>] ready to lead and code!
TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
: Employee(name, id),
Developer(name, id, language),
Manager(name, id, teamSize) {
// TODO: 输出构造函数消息
}
// TODO: 实现 showRole() 方法
// 应依次调用 getInfo()、code() 和 manage()
void showRole() {
// TODO: 调用这三个方法
}
// TODO: 实现析构函数
// 应输出:TeamLead [<name>] promoted out
~TeamLead() {
// TODO: 输出析构函数消息
}
};
int main() {
// 读取输入
string name;
int id;
string language;
int teamSize;
getline(cin, name);
cin >> id;
cin.ignore();
getline(cin, language);
cin >> teamSize;
// TODO:在块作用域内创建一个 TeamLead 对象
// 调用 showRole(),然后让对象离开作用域
{
// TODO:创建 TeamLead 对象并调用 showRole()
}
// 在代码块结束后输出最终消息
cout << "Organization restructured!" << endl;
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器