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.