如何使用 lambda 作为 std::unique_ptr 的删除器?

How to use lambda as std::unique_ptr's Deleter?

检查以下人为设计的程序:

#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对象应该被free了,不能引用了。如果我错了,请纠正我。所以我能得出的唯一安全方法是定义一个全局 dealloc 函数:

void dealloc(int* p)
{
    delete p;
}

但我不喜欢这种方法。

根据前面的说明,没有 100% 安全的方法将 lambda 用作 std::unique_ptr's Deleter,或者我误解了什么?如何将 lambda 用作 std::unique_ptrDeleter?

I think after leaving the func() scope, the dealloc object should be freed, and it can't be referenced.

你不用担心。是的,lambda 对象将被销毁,但是 lambda's function pointer conversion function 返回的指向函数的指针始终有效,它不会变得悬垂。

The value returned by this conversion function is a pointer to a function with C++ language linkage that, when invoked, has the same effect as invoking the closure object's function call operator directly.

如果您将 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

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.

因此,您的删除器正在使用指向函数的指针进行初始化,即使在您从 func.return 之后该函数仍然有效。

对前面的答案进行一些补充...

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 的使用,但开销很小但不必要。