unique_ptr的删除器不能直接传函数指针是因为其删除器类型是模板参数,编译期必须确定大小和调用方式;无捕获lambda可隐式转函数指针,有捕获lambda或状态仿函数需显式指定类型。

为什么 unique_ptr 的删除器不能直接传函数指针?
因为 unique_ptr 的删除器类型是模板参数,编译期必须确定大小和调用方式。裸函数指针(如 void(*)(int*))能隐式转成函数对象,但一旦你用了 lambda 或带状态的仿函数,就必须显式指定类型——否则编译器无法推导模板参数。
常见错误现象:error: no matching constructor for initialization of 'std::unique_ptr,往往是因为传了捕获型 lambda 却没写明删除器类型。
- 无捕获 lambda 可自动退化为函数指针,
unique_ptr能工作 - 有捕获 lambda(如
[&ctx](int* p){...})必须显式写全类型,且不能省略模板参数 - 自定义类删除器必须重载
operator(),且最好声明为noexcept
三种写法:函数指针、lambda、类删除器
最简场景用函数指针;需要访问外部变量时用 lambda;需要复用或封装逻辑时用类。
示例对比:
// 函数指针(无状态)
void my_delete(int* p) { delete p; }
std::unique_ptr<int void> ptr1(new int(42), my_delete);
<p>// 有捕获 lambda(必须显式指定类型)
auto del2 = [](int<em> p) { std::cout p ptr2(new int(100), del2);</em></p>
<p>// 类删除器(适合复用)
struct MyDeleter {
void operator()(int* p) const noexcept { delete p; }
};
std::unique_ptr<int mydeleter> ptr3(new int(200));</int></p></int>
- 函数指针方式最轻量,但无法携带上下文
- lambda 方式灵活,但每个实例类型都不同,不能跨
unique_ptr类型赋值 - 类删除器可定义为
inline或在头文件中完整定义,避免 ODR 违规
删除器里抛异常会怎样?
unique_ptr 析构时调用删除器,如果删除器抛异常而当前栈上已有未捕获异常(比如正在处理另一个异常),程序会直接调用 std::terminate。
- 所有删除器最好标记为
noexcept(包括 lambda 加noexcept后缀) - 若必须做可能失败的操作(如 close fd、flush buffer),应在删除器内吞掉异常或转为日志,不要往外抛
- 标准库的
default_delete就是noexcept的,自定义时保持一致更安全
资源不是 new 出来的怎么办?
比如用 malloc、fopen、pthread_mutex_init 分配的资源,删除器必须匹配释放方式。
典型例子:
// 管理 malloc 内存
std::unique_ptr<int void> ptr4(static_cast<int>(malloc(sizeof(int))), free);
<p>// 管理 FILE<em>
auto file_deleter = [](FILE</em> f) noexcept { if (f) fclose(f); };
std::unique_ptr<file decltype> ptr5(fopen("log.txt", "w"), file_deleter);</file></p></int></int>
- 注意
free和fclose本身不 throw,但要检查空指针(fclose(NULL)是未定义行为) - 不要混用分配/释放对:new + free、malloc + delete 都会触发未定义行为
- 系统资源(句柄、锁、socket)务必在删除器中显式清理,
unique_ptr不会自动帮你关
C++ 标准没规定删除器必须无状态,但实际使用中,带状态的删除器会让 unique_ptr 失去移动语义优势——拷贝构造被禁用,移动后原对象的删除器状态可能失效。除非真需要,否则优先选无状态方案。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











