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

Viewed 2704

Check following contrived program:

#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;
}

From std::function constructor manual, I think constructing std::function object may throw exception, even the ratio is very low:

UniPtr<int> func()
{
    auto dealloc = [](int* p){delete p;};

    return UniPtr<int>{alloc(), dealloc};
}

But if using function pointer instead of std::function object:

template<typename T>
using UniPtr = std::unique_ptr<T, void(*)(T*)>;

I think after leaving the func() scope, the dealloc object should be freed, and it can't be referenced. Please correct me if I am wrong. So the only safe method I can come out is defining a global dealloc function:

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

But I don't like this method.

Based on precedent exposition, there is not 100% safe way to use lambda as std::unique_ptr's Deleter, Or I misunderstand something? How to use lambda as std::unique_ptr's Deleter?

3 Answers

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

You don't need to worry about it. Yes the lambda object will be destroyed, but the pointer to function returned by the lambda's function pointer conversion function is always valid, it won't become dangled.

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.

If you defined UniPtr as

template<typename T>
using UniPtr = std::unique_ptr<T, void(*)(T*)>;

then the following code is valid, there are no concerns about the lifetime of the deleter

UniPtr<int> func()
{
    auto dealloc = [](int* p){delete p;};
    return UniPtr<int>{alloc(), dealloc};
}

Quoting 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.

So your deleter is being initialized with a pointer to function, which remains valid even after you return from func.

A little addition for implementation to the previous answers...

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);
}

using:

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; });
}

A little quick solution. It works in different scenarios. It avoids the use on std:function with its little but unnecessary overhead.

Related