Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can std::function be copied if it captures a unique_ptr?

Tags:

c++

#include <iostream>
#include <memory>
#include <functional>

struct PacketFrom
{
    typedef std::unique_ptr<PacketFrom> SPtr;
};

template<class P> 
class R{
    public:
    void queue_read(P *t)
    {
        doSomething([t = typename PacketFrom::SPtr(t)](){test(std::move(t));});
    }

    void doSomething(std::function<void()> f) {

    }

    void test(PacketFrom::SPtr p) {

    }
};

int main()
{
    R<PacketFrom> r;

    return 0;
}

How can this code work if the lambda function is being passed by copy? The lambda owns a unique_ptr which cannot be copied. I thought it was simply not capturing the unique_ptr so I added test() just to be sure it was capturing. However the code compiles fine.

like image 963
Guerlando OCs Avatar asked Dec 11 '19 17:12

Guerlando OCs


People also ask

What happens to Unique_ptr after move?

"Moving" transfers ownership to a new unique_ptr and resets the old unique_ptr .

Is std :: function copyable?

Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions (via pointers thereto), lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.

How does unique_ ptr work?

std::unique_ptr. std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.


2 Answers

This code doesn't actually "work" in that sense. It compiles, but only because you don't ever call queue_read.

If you try to use it (which forces the compiler to actually instantiate the R<P>::queue_read, which it doesn't have to do until that point) then you get an error from every compiler:

template<class P> 
class R{
    public:
    void queue_read(P *t)
    {
        doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });
    }

    void doSomething(std::function<void()> f) {
        (void)f;
    }

    void test(std::unique_ptr<P> p) {
        (void)p;
    }
};

int main()
{
    R<int> r;
    r.queue_read(new int(1));

    return 0;
}

clang 9.0:

error: call to deleted constructor of 'std::unique_ptr<int>'
    doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });

gcc 9.2:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
11 |         doSomething([this, t = std::unique_ptr<P>(t)]() { test(std::move(t)); });

MSVC 19.22:

error C2280: 'std::unique_ptr<P,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function

https://godbolt.org/z/KmjVJB (thanks Richard!)

Again, the key here is that the compiler didn't actually compile the code of queue_read because there was no need. The function is implicitly inline by virtue of being defined within the class body. Instantiating R<P> for some P causes only the declarations, but not the definitions of its member functions to be instantiated. Only once you actually call queue_read does the compiler have to complain.

This is a good thing by the way. You can use std::vector<MoveOnlyType> and do everything that doesn't require copying, even though some of the std::vector member functions require a copyable type. But as long as you never use the latter functions, everything is fine. If the compiler always instantiated all the member function definitions and reported errors in those (even if never used) it would be way more cumbersome.

like image 180
Max Langhof Avatar answered Oct 12 '22 07:10

Max Langhof


How can std::function be copied if it captures an unique_ptr?

A std::function doesn't capture anything.

A lambda that captures a non-copyable object such as std::unique_ptr is itself non-copyable. Such lambda doesn't satisfy, nor does any other non-copyable function object type satisfy the requirements of std::function which requires the functor to be copyable. Standard rule (from latest draft):

[func.wrap.func.con]

template<class F> function(F f);

Requires: F shall be Cpp17CopyConstructible.


However the code compiles fine.

This is typical when the ill-formed function is an unused function of a template. It should fail to compile if you attempt to call the function.

like image 38
eerorika Avatar answered Oct 12 '22 05:10

eerorika