C++ std::unique_ptr自定义删除器管理非内存资源
std::unique_ptr 默认使用 delete 或 delete[] 释放内存。但在处理文件句柄 (FILE*)、网络套接字、Windows 句柄 (HANDLE) 或数据库连接等非内存资源时,默认操作无效或会导致程序崩溃。通过自定义删除器,可以让 std::unique_ptr 在析构时自动调用特定的清理函数,实现资源的安全自动化管理。
定义带有函数对象的删除器。
这是处理 C 风格文件句柄最稳健的方法。定义一个结构体,重载 operator(),在其中编写资源释放逻辑。
#include <memory>
#include <cstdio>
struct FileDeleter {
void operator()(FILE* f) const {
if (f) {
std::printf("Executing file cleanup logic.\n");
std::fclose(f);
}
}
};
// 定义别名,方便后续使用
// 模板参数为:资源指针类型,删除器类型
using SmartFile = std::unique_ptr<FILE, FileDeleter>;
实例化智能指针并接管资源。
创建 SmartFile 对象,将原始资源指针传入构造函数。一旦 SmartFile 对象脱离作用域(无论是正常返回还是抛出异常),FileDeleter 都会自动执行。
void writeToFile(const char* filename) {
// fopen 返回原始 FILE*,所有权立即移交给 filePtr
SmartFile filePtr(std::fopen(filename, "w"));
// 检查资源是否有效
if (!filePtr) {
return; // 空指针不会触发 fclose
}
// 使用 .get() 获取原始指针进行操作
std::fprintf(filePtr.get(), "Important data line 1.\n");
std::fprintf(filePtr.get(), "Important data line 2.\n");
} // 函数结束时,filePtr 析构,自动调用 std::fclose(file)
使用 Lambda 表达式作为删除器。
如果删除器逻辑仅在局部使用,且不需要复用,定义一个结构体显得繁琐。使用 Lambda 配合 decltype 可以直接在声明时指定删除逻辑。
#include <memory>
#include <cstdio>
void processWithLambda() {
// 定义 Lambda:关闭文件并打印日志
auto closeFile = [](FILE* f) {
if (f) {
std::printf("Lambda closing file...\n");
std::fclose(f);
}
};
// 使用 decltype 推导 Lambda 类型
// 注意:必须将 Lambda 实例作为第二个参数传入构造函数
std::unique_ptr<FILE, decltype(closeFile)> file(std::fopen("log.txt", "w"), closeFile);
if (file) {
std::fprintf(file.get(), "Log entry via Lambda.\n");
}
// file 离开作用域,Lambda 被执行
}
注意:如果 Lambda 捕获了变量(例如 [&]),std::unique_ptr 的体积会增大以存储捕获的状态。不捕获变量的 Lambda 通常会被编译器优化为空对象,不会增加内存占用。
封装 Windows API 句柄。
Windows 开发中常见的 HANDLE (如 CreateFile 返回值) 也是一个需要通过 CloseHandle 释放的非内存资源。可以利用 unique_ptr<void, ...> 进行管理。
#include <windows.h>
#include <memory>
// 定义删除器类型:接受 HANDLE,调用 CloseHandle
struct HandleDeleter {
void operator()(HANDLE h) const {
if (h && h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
}
}
};
using ScopedHandle = std::unique_ptr<void, HandleDeleter>;
ScopedHandle createScopedHandle() {
HANDLE h = CreateFile(
"example.dat",
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
// 直接返回 unique_ptr 对象,发生错误时 h 可能是 INVALID_HANDLE_VALUE
// 虽然 unique_ptr 不会管理空指针,但我们的 HandleDeleter 已经检查了 INVALID_HANDLE_VALUE
return ScopedHandle(h);
}
掌握不同删除器类型对性能的影响。
自定义删除器的类型决定了 std::unique_ptr 对象的大小。在性能敏感或需要大量复制智能指针的场景下,这一点至关重要。
| 删除器类型 | 典型大小 (x64) | 适用场景 |
|---|---|---|
默认删除器 (std::default_delete) |
8 字节 (1个指针) | 管理 new / new[] 分配的内存 |
| 无状态 Lambda / 空结构体 | 8 字节 (1个指针) | 大多数自定义场景,编译器优化后无额外开销 |
| 函数指针 | 16 字节 (2个指针) | 需要动态指定删除函数时,会增加内存占用 |
| 带捕获的 Lambda | 8 + N 字节 | 需要在删除时访问上下文状态,N 为捕获变量大小 |
核心结论:为了保持 std::unique_ptr 的轻量级(等同于原始指针的大小),优先使用不捕获变量的 Lambda 或空结构体作为删除器。避免使用函数指针作为删除器类型,除非有动态绑定的特殊需求。

暂无评论,快来抢沙发吧!