结论:用 public 继承 + 虚函数接口 + 限制继承深度 ≤ 3 层,能覆盖 90% 可扩展场景,又不会掉进菱形继承、二义性、强耦合的坑;继承仅用于严格 is-a 关系,否则优先用组合;public 继承是默认选择,虚函数需按语义谨慎设计,避免菱形继承。

直接说结论:用 public 继承 + 虚函数接口 + 限制继承深度 ≤ 3 层,能覆盖 90% 可扩展场景,又不会掉进菱形继承、二义性、强耦合的坑。
什么时候该用继承,而不是组合
继承不是“能用就用”,它本质表达的是严格的 is-a 关系。比如 Dog 是 Animal,Circle 是 Shape——这种语义成立,才考虑继承。
如果只是“有一个”或“用到”,比如 Car 有 Engine,Student 有 Transcript,那就该用组合(成员变量)。
- 误用继承的典型症状:
Manager继承自Employee,又让Intern继承自Manager,再加个Contractor模糊挂靠……最后发现Intern::getSalary()该返回 0 还是抛异常? - 组合更灵活:
class Manager { Employee base; BonusPolicy policy; };——职责清晰,改BonusPolicy不影响Employee逻辑 - 继承一旦建立,修改基类接口(比如加个新虚函数)会强制所有子类响应;组合则只影响当前类
public / protected / private 继承怎么选
实际项目里,private 和 protected 继承极少需要,它们本质上是“实现复用”,但 C++ 有更好的替代:组合 + friend 或私有成员函数。
public 继承才是你该默认使用的——它明确表达了类型兼容性,支持多态,且符合直觉。
-
public:子类对象可安全赋值给基类指针/引用,std::unique_ptr<base> ptr = std::make_unique<derived>();</derived>合法 -
protected:子类能用,外部不能把Derived*当Base*用,等于自己封死了多态入口,不如直接组合 -
private:连子类的子类都访问不到基类成员,几乎等价于“把基类代码复制粘贴进来”,还多了构造顺序约束
虚函数和纯虚函数的边界在哪
别一上来就写 virtual,也别滥用 = 0。虚函数开销虽小,但它是多态契约的起点,意味着你要为所有子类预留扩展点。
- 基类中已有明确通用实现,且子类大概率不重写 → 普通函数(如
getName()) - 基类无法提供合理默认行为,必须由子类决定 →
virtual void draw() = 0;(纯虚) - 基类有合理默认,但允许子类选择性覆盖 →
virtual void save() { /* 默认存 JSON */ } - 注意:只要有一个虚函数,就该把析构函数也声明为
virtual ~Base() = default;,否则delete base_ptr会漏掉派生部分析构
为什么菱形继承要尽量避开
当 ClassD 同时继承 ClassB 和 ClassC,而二者又都继承自 ClassA,你就有了两份 ClassA 的副本。调用 ClassD::foo() 时,编译器不知道该走哪条路径,报错类似:request for member ‘foo’ is ambiguous。
虽然可以用 virtual 继承解决,但它带来额外成本:
- 每个虚继承子类对象多一个虚基类指针(通常 8 字节),内存开销不可忽略
- 构造顺序变得复杂:虚基类总是最优先构造,无论它在继承列表的位置
- 调试时看对象内存布局更难,IDE 往往不直观显示虚基类偏移
更务实的做法:把公共部分抽成独立类,用组合代替多继承。例如把 Serializable 和 Loggable 做成不含状态的策略类,class NetworkRequest { Serializable ser; Loggable log; }; ——清晰、无歧义、易测试。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











