13. 纯虚析构函数:纯虚析构函数的语法、为什么纯虚析构函数也需要实现?
说实话,纯虚析构函数这个话题,我当年刚学C++时也困惑过。
“既然是纯虚函数,那不就是抽象的吗?为什么还要实现?”
嗯,这个问题问得好。今天我们就把它彻底讲清楚。
13.1 纯虚析构函数的语法
先看一个最简单的例子:
class IShape {
public:
virtual ~IShape() = 0; // 纯虚析构函数声明
};
// 必须提供实现!
IShape::~IShape() {
// 清理基类资源
}
注意看,这里有两个关键点:
- 声明时用
= 0标记为纯虚函数 - 类外必须给出函数体
我见过不少新手只写了声明,忘了实现。结果链接器报错,一脸懵。
⚠️ 重要: 纯虚析构函数必须实现,否则链接会失败。这不是可选项,是强制要求。
13.2 为什么纯虚析构函数也需要实现?
这个问题,说白了跟C++的对象销毁机制有关。
你想想看,当派生类对象被销毁时,析构函数的调用顺序是什么?
- 先执行派生类自己的析构函数
- 再调用基类的析构函数
这个链条是编译器强制执行的。如果基类的析构函数没有实现,那第二步就卡住了。
我曾经在做一个插件系统时踩过这个坑。当时定义了一个抽象接口类:
class IPlugin {
public:
virtual void load() = 0;
virtual void unload() = 0;
virtual ~IPlugin() = 0; // 忘了实现
};
class MyPlugin : public IPlugin {
char* buffer;
public:
MyPlugin() { buffer = new char[1024]; }
~MyPlugin() { delete[] buffer; }
void load() override { /* ... */ }
void unload() override { /* ... */ }
};
int main() {
IPlugin* p = new MyPlugin();
delete p; // 链接错误!找不到 IPlugin::~IPlugin()
}
编译能过,链接时报错。我当时排查了半天,才意识到是纯虚析构函数没给实现。
💡 个人习惯: 我写抽象基类时,只要定义了虚析构函数,哪怕它是纯虚的,也会立刻在类外写上实现。这样就不会忘了。
13.3 什么时候需要用纯虚析构函数?
其实大多数情况下,我们只需要虚析构函数就够了。纯虚析构函数有它特定的使用场景。
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 普通抽象类(有纯虚函数) | 虚析构函数(非纯虚) | 够用,简单明了 |
| 工具类/接口类(无数据成员) | 纯虚析构函数 | 强制抽象,防止实例化 |
| 需要默认实现的基类 | 虚析构函数 | 提供默认清理逻辑 |
我个人习惯是:如果这个类除了析构函数外没有其他纯虚函数,那我就会用纯虚析构函数来让它变成抽象类。这样语义更清晰——"这个类就是用来被继承的,别想直接创建对象"。
13.4 纯虚析构函数的实现里该写什么?
嗯,这里有个常见的误解:有人觉得纯虚析构函数的实现是空的,那就干脆不写。
其实不然。实现体里可以放基类需要清理的资源:
class DatabaseHandler {
protected:
bool connected;
public:
DatabaseHandler() : connected(false) {}
virtual ~DatabaseHandler() = 0;
virtual bool connect() = 0;
virtual void disconnect() = 0;
};
// 基类析构:关闭连接
DatabaseHandler::~DatabaseHandler() {
if (connected) {
disconnect();
}
}
class MySQLHandler : public DatabaseHandler {
public:
bool connect() override {
connected = true;
// 连接MySQL...
return true;
}
void disconnect() override {
// 断开MySQL连接...
connected = false;
}
~MySQLHandler() {
// 派生类自己的清理
}
};
你看,基类的析构函数里做了"如果还连着就断开"的兜底操作。这就是纯虚析构函数实现的价值所在。
核心要点:
- 纯虚析构函数 = 抽象类 + 必须实现的析构逻辑
- 声明用
= 0,实现放类外 - 实现体可以为空,但不能不写
- 主要用于"只有析构逻辑需要抽象"的场景
13.5 避坑指南
最后分享几个我踩过的坑:
- 别在头文件里写实现: 纯虚析构函数的实现必须放在类外,写在头文件里会导致多重定义。
- 注意继承链: 如果基类有纯虚析构函数,派生类可以不实现它(因为基类已经实现了),但派生类自己的析构函数必须正确释放资源。
- 别滥用: 不是所有抽象类都需要纯虚析构函数。如果你的类已经有其他纯虚函数,用普通的虚析构函数就够了。
我曾经在一个项目里看到有人把所有接口类的析构函数都写成纯虚的。结果每个派生类都要额外注意基类析构的实现,反而增加了维护成本。说白了,工具要用在合适的地方。
好了,纯虚析构函数就讲到这里。记住它的本质:让类变成抽象的同时,强制你提供一个可用的析构函数实现。理解了这个,你就不会再困惑了。