纯虚函数定义:
virtual void release(void* ptr) const = 0;
virtual void* allocate(size_t byte_size) const = 0;对于上述代码第二行:返回值类型是 void*,不是 void
- void → 表示“没有类型 / 没有返回值”
- void* → 表示“指向未知类型对象的指针”
这里直接解引用是的非法的, 必须先转换类型:
void* p = alloc.allocate(16);
*p = 1; // ❌ 编译错误
int* ip = static_cast<int*>(p);
*ip = 42; // ✅= 0 的精确定义
纯虚函数(pure virtual function)表示:这个类本身不提供该函数的实现,任何可实例化的派生类都必须提供实现。
抽象类不能被实例化
DeviceAllocator a; // ❌ 编译错误
抽象类可以作为“接口类型”被指针 / 引用指向
std::unique_ptr<DeviceAllocator> p; // ✅
只要存在至少一个纯虚函数 (=0),这个类就是抽象类
派生类若不实现所有纯虚函数,也仍然是抽象类
纯虚函数是一种“强制接口契约(interface contract)”
- 返回类型、参数列表、const 限定符
- 都是 ABI / 接口规范的一部分
- 派生类必须 完全遵守 这个签名
虚函数有定义函数体和不定义两种,如果不定义的话,在派生类中有哪些方式去实现?
当一个**虚函数在基类中“没有函数体”**时,实际上只有两种情况:
| 情况 | 语法 | 是否必须在派生类实现 |
|---|---|---|
| 纯虚函数 | virtual f() = 0; |
必须 |
| 普通虚函数(声明但未定义) | virtual f(); |
必须(否则链接错误) |
👉 不存在第三种。
二、情况一:纯虚函数(= 0)——最常见、最规范
基类
struct Base {
virtual void f() = 0;
};含义:
Base 不提供实现
所有可实例化的派生类必须实现
派生类实现方式(唯一正确方式)
struct Derived : Base {
void f() override {
// 实现
}
};注意点:
-
override强烈建议写(不是必须,但几乎是业界标准) -
函数签名 必须完全一致
- 返回值
- 参数
constnoexcept(C++17+)
否则不算实现。
三、情况二:虚函数声明但未定义(非常危险)
基类
struct Base {
virtual void f(); // 注意:没有 =0
};这是合法的吗?
👉 语法合法
但它意味着:
Base 声称“我有一个虚函数 f”,
但你在任何地方都找不到它的实现。
如果派生类实现了 —— 可以工作
struct Derived : Base {
void f() override {
// 实现
}
};✔ 编译
✔ 链接
✔ 运行
如果派生类没有实现 —— 链接期炸
struct Derived : Base {
};错误不是编译错误,而是:
❌ 链接错误(undefined reference to Base::f)
因为:
- vtable 里需要一个
Base::f - 但你没提供任何实现
👉 这是 C++ 新手非常容易踩的坑
四、纯虚函数“也可以有定义”?(高阶知识点)
这是很多人不知道的,但你现在的理解水平已经可以接受这个事实了。
语法上是允许的:
struct Base {
virtual void f() = 0;
};
void Base::f() {
// 基类实现
}这是什么意思?
- Base 仍然是抽象类(因为
=0)- 派生类 仍然必须实现
- 但派生类可以显式调用基类版本
派生类调用基类纯虚实现
struct Derived : Base {
void f() override {
Base::f(); // 合法!
// 扩展逻辑
}
};👉 这种模式用于:
- 公共检查逻辑
- 通用前处理 / 后处理
- hook / template method 模式
五、派生类实现虚函数的“全部方式”总结
✅ 标准 override(最常见)
void f() override { ... }✅ 不写 override(合法但不推荐)
void f() { ... }❌ 签名不一致(不会覆盖)
void f() const; // ❌ 如果基类没有 const结果:
- 不是 override
- 基类纯虚函数仍未实现
- 派生类仍是抽象类
✅ 使用 final(禁止再被重写)
void f() override final { ... }