纯虚类是编译期强制实现的类型契约,须含虚析构、无数据成员、全纯虚函数,用于定义正交接口如Drawable;新增函数需新接口类以保ABI兼容。

只含纯虚函数的类适合定义契约型接口
它不是为了复用代码,而是为了在编译期强制约束行为边界。这类类本质是“类型契约”——只要继承它,就必须实现全部函数,否则连编译都过不去。
典型场景包括:图形绘制(Drawable)、序列化(Serializable)、资源管理(Resource)等需要统一调用入口、但底层实现千差万别的领域。
- 所有成员函数必须声明为
virtual void func() = 0,不能有普通成员变量(可含static const常量) - 析构函数必须是虚的,且要有实现(哪怕空的),否则通过基类指针
delete会未定义行为 - 不提供任何默认实现,避免派生类误以为“可以不重写”
为什么不能加普通虚函数或数据成员
加一个带默认实现的虚函数,就不再是纯粹的接口,而退化成“半抽象基类”。这会让使用者困惑:哪些必须重写?哪些可以跳过?编译器也不再帮你检查完整性。
加非静态数据成员更危险——不同派生类可能对同一字段语义理解不同,破坏接口的正交性;还会让对象大小不可控,影响多态数组或内存池布局。
立即学习“C++免费学习笔记(深入)”;
- 接口类应满足
std::is_polymorphic_v<t></t>且std::is_abstract_v<t></t>,但std::is_standard_layout_v<t></t>不必为 true - 若需共享状态,应通过组合(如传入
std::shared_ptr<context></context>)而非继承 - 想提供默认逻辑?另建一个模板基类(如
DefaultDrawable)供选择性继承,别污染接口本身
如何写一个合规的纯接口类
关键不是语法正确,而是意图清晰。下面这个 Logger 接口看起来简单,但每个细节都有讲究:
class Logger {
public:
virtual ~Logger() = default; // 必须有虚析构,且推荐=default而非{},更明确
<pre class="brush:php;toolbar:false;">virtual void log(const char* msg) = 0;
virtual void error(const char* msg) = 0;
virtual void warn(const char* msg) = 0;protected: Logger() = default; // 防止外部直接构造,但允许派生类调用 };
- 不用
public:后面跟一堆virtual,容易漏掉= 0;建议每行一个函数声明 - 禁止使用
inline或函数体(哪怕空的),否则不再是纯虚 - 参数尽量用
const char*或std::string_view,避免强制拷贝或生命周期绑定问题
容易被忽略的 ABI 兼容性陷阱
一旦发布为库接口,哪怕只增一个纯虚函数,也会破坏二进制兼容性——vtable 偏移全变,旧客户端链接新库时可能 crash。这不是 C++ 标准问题,而是实际工程中高频踩坑点。
- 新增接口函数?必须定义新接口类(如
LoggerV2),而不是往老类里塞 - 想扩展参数?用结构体传参(
struct LogOptions { int level; bool sync; };),保留原有函数签名 - 头文件中避免依赖具体 STL 类型(如
std::string),优先用裸指针/视图,降低跨编译器风险
接口越稳定,后续越省心;但第一版就想穷尽所有需求,反而会导致设计僵化。真正难的不是写 = 0,是在需求模糊时,忍住不提前加函数。


















