Home > Software engineering >  C lambda capture list by value or by reference doesn't give me different results
C lambda capture list by value or by reference doesn't give me different results

Time:07-12

I am having the below code :

std::vector<std::function<void()>> functors;

class Bar
{
    public :
        Bar(const int x, const int y):d_x(x),d_y(y){}
        
        ~Bar(){
            cout << "Destructing Bar" << endl;
        }
    
        void addToQueue()
        {
            const auto job = [=](){
                cout << "x:" << d_x << " y: " << d_y; 
            };
            functors.push_back(job);
        }
        
    private :
        int d_x,d_y;
};


void example()
{
    cout << "Hello World" << endl;
    {
        shared_ptr<Bar> barPtr = make_shared<Bar>(5,10);
        barPtr->addToQueue();
    }
    cout << "Out of scope. Sleeping" << endl;
    usleep(1000);
    functors[0]();
}

The output is as expected :

Hello World
Destructing Bar
Out of scope. Sleeping
x:5 y: 10

I am now capturing by value, which is why I assume when the Bar object gets destroyed, I can still access its member variables. If the above is right, I am expecting the below change to give me UB:

const auto job = [&](){

However, I still see the same result. Why is that? Have i understood something wrong?

EDIT Further on the above, what I want to understand from this example - is how can I have access to a class member variables in a lambda function even if object has been destroyed? I am trying to avoid UB and thought that passing by value is the way to go, but can't prove that the opposite isn't working.

CodePudding user response:

This kind of confusion iss probably one of the reasons why C 20 deprecated the implicit capture of this with [=]. You can still capture [this], in which case you have the usual lifetime issues with an unmanaged pointer. You can capture [*this] (since C =17), which will capture a copy of *this so you don't have lifetime issues.

You could also use std::enable_shared_from_this since you're using a std::shared_ptr in example, but that's a bit more complicated. Still, it would avoid both the copy and the UB when the lifetime issues.

CodePudding user response:

In these examples you are capturing this and not any of the fields.
When capturing this, by design, it is never captured by copying the object or the fields.
The best way to capture a field by value is:

[field = field] () { }

CodePudding user response:

Both versions of your code have undefined behaviour. barPtr is the only owner of the shared_ptr so your object is destructed at the end of the scope containing barPtr. Executing the lambda which has captured this from the object in barPtr has undefined behaviour.

The usual way to prevent this is for the lambda to capture a shared_pointer from shared_from_this to keep the object alive. E.g:

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

std::vector<std::function<void()>> functors;

class Bar : public std::enable_shared_from_this<Bar>
{
    public :
        Bar(const int x, const int y):d_x(x),d_y(y){}
        
        ~Bar(){
            std::cout << "Destructing Bar\n";
        }
    
        void addToQueue()
        {
            auto self = shared_from_this();
            const auto job = [this, self](){
                std::cout << "x:" << d_x << " y: " << d_y << "\n"; 
            };
            functors.push_back(job);
        }
        
    private :
        int d_x,d_y;
};


int main()
{
    std::cout << "Hello World\n";
    {
        std::shared_ptr<Bar> barPtr = std::make_shared<Bar>(5,10);
        barPtr->addToQueue();
    }
    std::cout << "Out of scope\n";
    functors[0]();
}

By capturing self the shared_ptr will now survive for at least as long as the lambda does.

  • Related