Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 922
Nan Xiao Avatar asked Sep 05 '18 03:09

Nan Xiao


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

like image 50
songyuanyao Avatar answered Oct 26 '22 11:10

songyuanyao


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.

like image 37
Praetorian Avatar answered Oct 26 '22 09:10

Praetorian