纯虚函数必须声明为virtual void func() = 0;且不能有函数体,否则编译报错;抽象类因含纯虚函数而不可实例化,但可作指针/引用类型;子类须实现全部纯虚函数才非抽象;接口设计应仅对必须差异化实现的行为设纯虚。

纯虚函数怎么写才真抽象
纯虚函数不是“加个 = 0 就完事”,它本质是强制子类实现的契约。写成 virtual void draw() = 0; 没问题,但若在基类里同时提供了定义(比如 virtual void update() = 0 { /* 实现 */ }),编译器会直接报错——error: pure specifier on function with body。
常见错误是误以为“= 0”只是语法糖,结果在头文件里忘了删掉函数体;或者把 const / noexcept 等修饰符漏掉,导致子类重写时签名不匹配,变成隐藏而非覆盖。
- 纯虚函数声明后不能有函数体,哪怕空花括号也不行
- 可以带 const / noexcept / final 等限定符,子类重写必须严格一致
- 构造函数里调用纯虚函数会导致未定义行为,运行时大概率崩溃(
pure virtual function call)
抽象类为什么不能实例化
因为抽象类至少有一个纯虚函数,C++ 标准规定:含有纯虚函数的类是抽象类,禁止 new A() 或 A a;。这不是编译器偷懒,而是逻辑上说不通——如果一个类连 draw() 都没定义清楚,你让它画什么?
但要注意:抽象类可以有构造函数、成员变量、甚至非纯虚函数。它的对象无法直接创建,但可以作为指针或引用类型存在,这才是它的真实用途。
立即学习“C++免费学习笔记(深入)”;
-
A* p = new B();合法,A a;编译失败 - 派生类只要没实现全部纯虚函数,它自己仍是抽象类
- 析构函数如果是纯虚的,必须提供定义(哪怕空实现),否则链接失败
接口层设计时,哪些函数该设为纯虚
只把真正需要多态分发、且不同实现逻辑差异大的行为设为纯虚。比如图形渲染中的 render()、网络模块里的 send()、序列化中的 serialize()。别把 get_id() 或 is_valid() 这种通用逻辑也拉进来——它们更适合默认实现或普通虚函数。
滥用纯虚函数会让继承树僵硬。比如给所有子类强加一个 save_to_disk(),但有些实现根本不需要持久化,那就得写个空实现,违背了接口的语义清晰性。
- 问自己:“这个操作是否每种子类都必须以完全不同的方式完成?”
- 优先用组合+策略模式替代深度继承,尤其当行为正交时(如压缩+加密+校验)
- 纯虚函数名要动词化、无歧义,避免
do_something()这类占位符
动态绑定失效的几个典型场景
即使写了纯虚函数,多态也可能悄悄失效。最常见的是通过对象值传递(void func(A a)),触发对象切片——派生类部分被砍掉,只剩基类副本,调用的永远是基类版本(如果有的话)或直接编译失败。
另一个坑是返回类型协变没配对:基类声明 virtual A* clone() = 0;,子类想返回 B*,但忘了在子类中写 B* clone() override,结果编译器当成新函数,而不是重写。
- 必须用指针或引用传参/返回,才能触发动态绑定
- 子类重写时返回类型可以协变(如基类返回
Base*,子类可返回Derived*),但参数列表必须完全一致 - 忘记加
override关键字,改错签名也不会报错,只会静默变成新函数
接口层最难的不是语法,是判断哪些行为真的需要被“约定死”。写满纯虚函数的基类,往往说明职责没拆清,或者过早抽象了还没稳定的需求。










