一。前言
在上篇中,我们已经系统地梳理了 C++ 单继承的基本规则:访问权限、构造与析构顺序、成员隐藏等问题,本质上都围绕着派生类对象中包含一个完整的基类子对象这一事实展开。
然而,当继承关系不再是一对一,而是多个基类同时存在时,事情就开始变得复杂起来。
- 同名成员该访问哪一个
- 一个对象里到底有几个基类子对象
- 为什么会出现二义性
- 编译器又是如何通过虚继承解决这些问题的
这些问题几乎都集中在多继承与菱形继承场景中,也是 C++ 继承机制最容易被误解、最容易出错的部分。
因此,在这一篇中,我们将重点讨论:
- 多继承的对象布局与二义性问题
- 菱形继承产生的根本原因
- 虚继承的设计目的与工作原理
理解了这些内容,才能真正走向理解继承在底层是如何工作的。
二。基类与派生类之间的转换
在 public 继承体系中,派生类不仅包含了自己新增的成员,还完整地包含了基类的成员。基于这种关系,C++ 允许在基类和派生类之间进行特定的类型转换。
假如我们有一个 Person 类和一个 Student 类:
class Person { public: std::string _name; int _age; bool _sex; };
class Student : public Person { public: int _grade; };
1. 向上转型(Upcasting)与形象的切片
我们可以将派生类对象赋值给基类的对象 / 指针 / 引用。在 C++ 中,这个过程有一个非常形象的叫法——切片(切割)。
- 对象赋值:如果是赋值给基类对象(Base b = d;),派生类特有的成员会被真正切掉丢弃,只保留基类部分。
- 指针/引用赋值:如果是赋值给基类指针或引用(Base* pb = &d;),并不会发生真正的数据丢失,只是指针的视野被限制在了切出来的基类那部分。
Student stu; // 1. 切片(向上转型):天然支持,非常安全
Person per1 = stu; // 对象切片:派生类成员被丢弃,只把基类成员拷贝
Person* per2 = &stu; // 指针切片:stu 指向 per2 中的基类部分
Person& per3 = stu; // 引用切片:stu 引用 per3 中的基类部分
2. 严禁反向赋值:基类对象不能赋值给派生类对象
这是一个硬性规定。因为派生类通常比基类拥有更多的成员变量。如果允许把基类对象赋值给派生类对象,派生类中多出来的那部分成员将处于未初始化状态,这是极其危险的,因此编译器会直接报错。
// 2. 错误示范:基类对象绝对不能赋给派生类对象
Person per;
Student stu;
stu = per; // 编译报错
3. 向下转型(Downcasting):指针/引用的强制转换
基类对象不行,那基类的指针或引用可以赋值给派生类吗?
语法上可以通过强制类型转换将基类指针/引用转为派生类指针/引用。
只有当这个基类指针原本就指向一个派生类对象时,这种转换才是安全的。如果它本来指向的就是个纯基类对象,强转后去访问派生类成员就会导致越界崩溃。


