Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to pass const reference to temporary/annonymous lambda into std::thread constructor?

Following on from this question: can-a-temperary-lambda-by-passed-by-reference?

I have a fixed code snippet:

// global variable
std::thread worker_thread;

// Template function
template <typename Functor>
void start_work(const Functor &worker_fn)  // lambda passed by const ref
{
    worker_thread = std::thread([&](){
        worker_fn();
    });
}

This is called like this:

void do_work(int value)
{
    printf("Hello from worker\r\n");
}

int main()
{
    // This lambda is a temporary variable...
    start_work([](int value){ do_work(value) });
}

This seems to work, but I am concerned about passing a temporary lambda into the thread constructor since the thread will run, but the function start_work() will return and the temp-lambda will go out of scope.

However I was looking at the std::thread constructor which is defined:

thread() noexcept; (1) (since C++11)

thread( thread&& other ) noexcept; (2) (since C++11)

template< class Function, class... Args > explicit thread( Function&& f, Args&&... args ); (3) (since C++11)

thread(const thread&) = delete; (4) (since C++11)

So I am assuming that the constructor 3 is called:

template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

I struggle to understand what is written here, but it looks like it would try to move the lambda && which I believe is ok for temp variables.

So is what I have done in my code snippet dangerous (i.e. the ref goes out of scope) or correct (i.e. the temp is moved and all is well)? or neither??

An alternative is to just pass my value (make a copy) which is not so bad anyway in this case.

like image 766
code_fodder Avatar asked Apr 23 '18 14:04

code_fodder


2 Answers

A temporary is indeed moved but it is the "inner" one, the argument to std::thread.

That temporary holds a reference to the "outer" temporary, the argument to start_work, and whose lifetime ends after start_work has returned.

Thus, your "inner" lambda object holds a reference to an object that may or may not exist during its execution, which is very unsafe.

like image 88
molbdnilo Avatar answered Nov 18 '22 23:11

molbdnilo


A lambda is an anonymous struct in C++. If we were to translate the snippet to an equivalent one without lambdas, it would become

template <typename Functor>
void start_work(const Functor &worker_fn)
{
    struct lambda {
        const Functor& worker_fn;
        auto operator()() const { worker_fn(); }
    };
    worker_thread = std::thread(lambda{worker_fn});
}

lambda has a non-stack-based const reference as a member, which would dangle as soon as start_work returns, regardless whether the lambda object itself is copied.

like image 41
Passer By Avatar answered Nov 19 '22 00:11

Passer By