std::unique_ptr自定义删除器必须显式指定删除器类型作为第二模板参数,否则编译失败;常见错误是只传删除器对象而未修改模板参数;Lambda、函数指针、struct均可作删除器,但需注意捕获生命周期、类型匹配及数组特化问题。

std::unique_ptr自定义删除器的语法结构
必须显式指定删除器类型,否则编译失败——std::unique_ptr 的模板参数不是可选的。默认构造时用的是 std::default_delete,一旦要换,就得把删除器类型作为第二个模板参数写出来。
常见错误是只传了删除器对象,却没改模板参数:
// ❌ 错误:类型不匹配,编译不过
std::unique_ptr<int> ptr(new int(42), [](int* p) { delete p; });
<p>// ✅ 正确:显式声明删除器类型
std::unique_ptr<int, void(<em>)(int</em>)> ptr(new int(42), [](int* p) { delete p; });
- 函数指针作删除器:模板第二参数写成
void(*)(T*)或更泛的std::function<void(T*)> - Lambda 作删除器:若含捕获(如
[&ctx]),不能转为函数指针,必须用std::function或直接作为模板参数推导(推荐用类模板参数自动推导) - 自定义 struct 删除器:需重载
operator(),类型简洁且零开销
用 lambda 作删除器时的生命周期陷阱
lambda 若捕获局部变量,而 std::unique_ptr 生命周期长于该变量,析构时调用删除器就会访问悬垂引用。
典型场景:在函数内创建带捕获 lambda 的 std::unique_ptr,然后返回它——此时 lambda 捕获的栈变量早已销毁。
立即学习“C++免费学习笔记(深入)”;
- 避免捕获局部变量;改用值捕获(
[=])并确保捕获的对象能安全持有 - 更稳妥的做法是用无捕获 lambda(
[]),或把上下文封装进堆对象再传给删除器 - 若必须用引用捕获,请确认
std::unique_ptr不会活过被引用对象的生命周期
struct 删除器比 std::function 更高效
用 std::function 包装删除器会引入类型擦除开销(虚调用、堆分配可能),而 struct 删除器是零成本抽象。
例如管理 C 风格资源:
struct CFileDeleter {
void operator()(FILE* f) const { if (f) fclose(f); }
};
std::unique_ptr<FILE, CFileDeleter> fp(fopen("log.txt", "w"));
- struct 删除器支持
constexpr构造、无状态、可内联,编译器几乎能完全优化掉调用开销 -
std::function<void(FILE*)>占用更大内存(通常 32 字节),且调用有间接跳转成本 - 如果删除逻辑简单固定,优先选 struct;仅当需要运行时切换行为时才考虑
std::function
数组特化版本的删除器容易被忽略
处理数组时,std::unique_ptr<T[]> 的默认删除器调用 delete[],但自定义删除器不会自动适配——你得自己保证调用 delete[] 而非 delete。
- 不要对
T[]类型使用void(*)(T*)类型的删除器;应为void(*)(T*)且内部调用delete[] - 更安全的方式是用偏特化:显式写
std::unique_ptr<int[], void(*)(int*)>,并在 lambda 里写delete[] p - struct 删除器同样要注意:对数组类型,
operator()(T* p)必须显式调用delete[] p
真正麻烦的是混用:比如用 new int[10] 分配,却用只调 delete 的删除器,结果未定义行为——连 ASan 都不一定能抓到。


















