不实现所有纯虚函数,派生类仍是抽象类;必须全部正确重写(签名含const等限定符一致),否则无法实例化,编译报错。override可提前捕获签名错误,纯虚函数可有类外定义但不改变抽象性,析构函数例外须定义。

不实现所有纯虚函数,派生类仍是抽象类
是的,必须全部实现——但“必须”不是编译器的劝告,而是硬性限制:只要有一个纯虚函数没被正确重写,该派生类就无法实例化,new Derived() 或 Derived d; 都会触发编译错误。
关键点在于“正确重写”:签名必须完全一致,包括 const、&、&&、noexcept 等限定符。例如基类声明 virtual void render() const = 0;,子类只写 void render() {}(缺 const)→ 这不是重写,而是新增一个重载函数,纯虚函数依然未实现。
- 编译器报错典型信息:
error: cannot declare variable 'd' to be of abstract type 'Derived' - 用
override关键字能提前捕获签名不匹配问题,强烈建议加上 - 即使子类只新增了少量功能,也得把所有纯虚函数都补全,哪怕先写空实现
{}
纯虚函数可以有函数体,但不影响抽象性
纯虚函数允许在类外定义实现(极少见),比如 virtual void draw() const = 0; 声明后,在类外写 void Shape::draw() const { /* default impl */ }。但这不改变 Shape 是抽象类的事实,也不免除派生类重写的义务。
这种写法实际用途有限,常见于提供可选默认行为的框架接口,但调用它需显式通过作用域解析(shape->Shape::draw()),不能靠多态自动触发。
- 类内直接写函数体(如
virtual void f() = 0 { })是非法语法,C++ 不允许 - 纯虚析构函数例外:必须提供定义(哪怕为空),否则链接失败,但声明仍为
virtual ~Base() = 0; - 有函数体 ≠ 可跳过重写 —— 编译器只看声明处有没有
= 0,不看有没有定义
继承链中中间类可以不实现,但最终实例化类必须全实现
如果存在多级继承(A → B → C),其中 A 是抽象类,B 继承 A 但未实现全部纯虚函数,那么 B 自动成为抽象类;此时 C 只需实现 A 中未被 B 实现的那些纯虚函数即可——不需要重复实现 B 已覆盖的部分。
但要注意:一旦 B 尝试实现某个纯虚函数却签名出错(比如漏了 const),那个函数在 B 中就未被真正覆盖,C 仍需负责它。
-
B中用override能立刻暴露覆盖失败,避免把问题拖到C层才报错 - 用
static_assert(!std::is_abstract_v<b>, "")</b>可在编译期主动检查B是否意外成了抽象类 - 抽象类可含构造函数、普通成员函数、甚至非纯虚函数,这些都不影响“必须实现纯虚函数”这一规则
为什么析构函数声明为 virtual 很重要,但它不是“纯虚”的替代方案
常见误解:以为只要写了 virtual ~Base() {},这个类就是抽象类。错。没有 = 0 的虚函数只是虚函数,不是纯虚函数;类仍可实例化。
虚析构函数的唯一目的是确保通过基类指针删除派生类对象时,能正确调用派生类析构函数。它和抽象性无关,也不能代替纯虚函数来阻止实例化。
- 正确组合是:
virtual ~Base() = default;+ 至少一个virtual void f() = 0; - 若只写
virtual ~Base() = 0;(纯虚析构),类确实是抽象类,但你必须在类外提供定义,否则链接失败 - 纯虚析构函数体必须存在,例如:
Base::~Base() {}—— 这是少数必须定义纯虚函数的场景
最易忽略的一点:纯虚函数的“纯”只由 = 0 决定,和是否提供定义、是否带 override、是否在构造/析构中调用都无关。编译器认准这个标记,不多看一眼。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











