检查以下人为的程序:
#include <functional>
#include <memory>
template<typename T>
using UniPtr = std::unique_ptr<T, std::function<void(T*)>>;
int* alloc()
{
return new int;
}
UniPtr<int> func()
{
auto dealloc = [](int* p){delete p;};
return UniPtr<int>{alloc(), dealloc};
}
int main()
{
auto p = func();
return 0;
}
从std::function constructor manual,我认为构建std::function
对象可能会抛出异常,即使比率非常低:
UniPtr<int> func()
{
auto dealloc = [](int* p){delete p;};
return UniPtr<int>{alloc(), dealloc};
}
但是如果使用函数指针而不是std::function
对象:
template<typename T>
using UniPtr = std::unique_ptr<T, void(*)(T*)>;
我想在离开func()
范围后,应释放dealloc
对象,并且不能引用它。如果我错了,请纠正我。所以我能说出的唯一安全方法是定义一个全局dealloc
函数:
void dealloc(int* p)
{
delete p;
}
但我不喜欢这种方法。
根据先例说明,使用lambda
作为std::unique_ptr's Deleter没有100%安全的方法,或者我误解了什么?如何使用lambda
作为std::unique_ptr
的Deleter
?
我想在离开
func()
范围后,应释放dealloc
对象,并且不能引用它。
你不必担心它。是的lambda对象将被销毁,但lambda's function pointer conversion function返回的函数指针始终有效,它不会被悬空。
此转换函数返回的值是指向具有C ++语言链接的函数的指针,该函数在调用时具有与直接调用闭包对象的函数调用操作符相同的效果。
如果你将UniPtr
定义为
template<typename T>
using UniPtr = std::unique_ptr<T, void(*)(T*)>;
那么下面的代码是有效的,没有关于删除器的生命周期的担忧
UniPtr<int> func()
{
auto dealloc = [](int* p){delete p;};
return UniPtr<int>{alloc(), dealloc};
}
引用N3337,expr.prim.lambda/6
没有lambda-capture的lambda表达式的闭包类型有一个公共的非虚拟非显式
const
转换函数,指向函数,该函数具有与闭包类型的函数调用操作符相同的参数和返回类型。此转换函数返回的值应为函数的地址,该函数在调用时与调用闭包类型的函数调用运算符具有相同的效果。
因此,您的删除器正在使用指向函数的指针进行初始化,即使从func
返回后该指针仍然有效。
对以前的答案实施一点点补充......
template<typename T, typename D>
std::unique_ptr<T, D> make_unique_with_deleter(T* t, D d)
{
// granted copy elison since C++17
return std::unique_ptr<T, D> (t, d);
}
使用:
class A
{
};
auto up1 = make_unique_with_deleter(new A, [](A* a) {delete a; });
auto up2 = make_unique_with_deleter(std::fopen("something", "w"), std::fclose);
{
int any_value = 0;
// caution: only local use with lambda-capture, but possible
auto up3 = make_unique_with_deleter(new A, [any_value](A* a) {delete a; });
}
一点点快速解决方案。它适用于不同的场景。它避免了在std:function上的使用,只需要很少但不必要的开销。