3

I have a function which enqueue lambda using std::queue's build-in emplace. I created shared_ptr object (task), which later I would capture in lambda.

    template<typename Func, typename... Args>
    auto submit(Func&& f, Args&&... args)
    {
        using result_type = std::result_of_t<Func(Args...)>;
        using pckg_task_type = std::packaged_task<result_type()>;

        auto task = std::make_shared< pckg_task_type >(
                    std::bind(std::forward<Func>(f), std::forward<Args>(args)...) );
        ...
    }

This is the part which is creating confusion:

Case 1: Which works perfectly

            tasks.emplace(
                [task](){
                    auto wptr = std::weak_ptr<pckg_task_type>(task);
                    if( auto p = wptr.lock() )
                    {
                        (*p)();
                    }
                    else
                        throw std::runtime_error("weak error");
                }
            );

Case 2: Which immediately throws an exception

                tasks.emplace(
                [wc = std::weak_ptr<pckg_task_type>(task)](){
                    if( auto p = wc.lock() )
                    {
                        (*p)();
                    }
                    else
                        throw std::runtime_error("weak error");
                }
            );

With tasks defined as

std::queue< std::function<void()> > tasks;

Calling lambda from case 2 without emplace it into queue doesn't throw exception.

Can anyone explain the difference between cases above? Where is the problem?

donjuedo
  • 2,475
  • 18
  • 28
VanDyke
  • 31
  • 1
  • 3

2 Answers2

3

The problem is that in the case 2 the task shared pointer is not saved anywhere and the weak pointer wc that is saved in the lambda capture expires as soon as you return from submit. When the lambda is invoked, wc.lock() returns a null pointer.

You probably need to save the task shared_ptr somewhere for the lifetime duration of the enqueued function object. Or you don't need weak_ptr at all and can just save the task shared_ptr in the lambda capture and use it directly in the lambda body.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
2

Your first case copies the shared pointer into the lambda and will therefore keep it alive for the lifetime of the lambda, the weak pointer inside the lambda is pointless as you create a weak pointer then immediately lock it which will never fail.

The second case is only capturing a weak pointer, the shared pointer goes out of scope at the end of submit and presumably there are no other references to it so the pointer is deleted. This would only work if your lambda starts executing and locks the pointer before the end of submit.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60