适合做基类的核心依据是:有虚析构函数、定义了公有/保护虚函数(至少一个)、显式控制拷贝/移动语义、具备清晰的继承契约。虚析构是硬性门槛,否则 delete 基类指针引发未定义行为;无虚函数则无法多态;未禁用拷贝易致浅拷贝问题;缺乏契约文档则语义不明确。

一个类是否适合做基类,不取决于它“长得像不像基类”,而取决于它是否能被安全、正确地继承和多态使用。核心判断依据是:有没有虚析构函数、是否禁止拷贝/移动、是否定义了虚函数、以及是否满足继承契约的语义要求。
有没有虚析构函数
这是最硬性的门槛。若类预期被继承,~Base() 必须是虚函数(或 = default 在 public 区域),否则通过基类指针 delete 派生对象会触发未定义行为(UB)。
- 常见错误:只加了
virtual void foo() = 0;却忘了析构函数 —— 纯虚函数不自动让析构虚化 - 推荐写法:
virtual ~Base() = default;(简洁、安全、符合 C++11 起最佳实践) - 如果类明确禁止继承,应加
final;若连final都没加,但析构非虚,那它“技术上可继承,语义上不该继承”,属于设计缺陷
是否定义了虚函数(尤其是虚析构以外的)
有虚函数 ≠ 适合做基类,但没有虚函数几乎一定不适合——因为无法支持多态调用,派生类重写无意义,dynamic_cast 和 typeid 也失效(RTTI 不启用)。
- 仅含非虚成员函数的类(哪怕有虚析构)只是“可安全销毁”,不是“可多态使用”
- 真正构成多态接口的,至少要有一个公有虚函数(如
virtual void draw() = 0;),且通常应为纯虚(= 0)以强制派生类实现 - 注意:虚函数必须是
public或protected,private虚函数无法被派生类重写(编译报错)
拷贝/移动语义是否被显式控制
基类若允许值语义(比如能被 std::vector<base> 存储),需明确定义拷贝/移动行为;但多数抽象基类应禁用拷贝,只允许移动或完全禁止。
- 典型做法:删除拷贝构造与赋值:
Base(const Base&) = delete;、Base& operator=(const Base&) = delete; - 若支持移动(如资源持有型基类),应显式默认移动操作:
Base(Base&&) = default;,并确保移动后状态可析构 - 不写任何拷贝/移动声明 → 编译器自动生成(可能引发浅拷贝问题),对基类是高危行为
是否有清晰的继承契约(非语法,而是语义)
编译器不管这个,但人必须管。适合做基类,意味着它定义了“什么必须做、什么可以改、什么绝不能碰”。这体现在文档、注释甚至代码结构里。
- 纯虚函数是契约核心:
virtual int serialize() = 0;表示“所有派生类必须提供序列化逻辑” - 非虚公有函数可能是模板方法(Template Method)模式的一部分,内部调用虚函数,这类函数不应被重写 —— 可加
final修饰 - 保护成员变量(
protected:)是强信号:你预期派生类直接访问/修改这些状态;若全是private+ 公有虚函数,则更偏向组合而非继承 - 没有
protected成员、也没有虚函数、只有公有非虚接口的类,大概率不是为继承设计的(比如std::string)
最容易被忽略的一点:基类的构造函数参数设计。如果基类构造函数需要大量参数,或者参数含义模糊(比如 Base(int, bool, double)),派生类构造将变得脆弱且难维护。真正健壮的基类,往往把初始化逻辑收束到虚函数或工厂中,或提供带名参数的构造方式(如 Builder 模式)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











