Reputation: 183
Suppose that we have a STL container with some objects, and these objects can post functions to a queue to be executed later. But before these functions get executed, the container gets modified in such a way that pointers pointing to that object are invalidated. Let me illustrate with an example:
#include <vector>
#include <functional>
class Class_A
{
public:
std::function<void()> getFunctionToRunLater()
{
return [this] () { somethingToDo(); moreThingsToDo(); };
// Returns a lambda function that captures the this pointer,
// so it can access the object's methods and variables.
}
void somethingToDo();
void moreThingsToDo();
}
int main()
{
std::vector<Class_A> vec;
vec.push_back(Class_A());
std::function<void()> pendingFunction = vec.back().getFunctionToRunLater();
// More code...
pendingFunction();
}
Everything fine, right? We get a function the object wants to run and, after some logic, we execute that function. This represents posting functions to a queue and them execute all functions in the queue. But now look at this one:
int main()
{
std::vector<Class_A> vec;
vec.push_back(Class_A());
std::function<void()> pendingFunction = vec.back().getFunctionToRunLater();
// More code...
vec.reserve(1000);
// This will surely reallocate the vector, invalidating all pointers.
pendingFunction();
// And now my program is going straight down to hell, right?
}
Is my assumption correct? What will happen if the lambda doesn't capture anything at all, will the program still be logically broken? And what about if the lambda doesn't capture the this pointer, but rather some other class field specifically?
Upvotes: 8
Views: 881
Reputation: 6496
The existing answer already mentions that the pointer can be invalidated. One way to avoid the problem is, as already mentioned, changing the ownership of *this
by either shared_ptr
, unique_ptr
or a copy. However, this comes at extra cost (dynamic allocation or extra copy) and sometimes is simply not possible (non-copyable types).
Instead, I would suggest a design that doesn't lead to this problem in the first place, i.e. not making the this
pointer part of the lambda's state. Take the object as a parameter:
std::function<void(Class_A&)> getFunctionToRunLater()
{
return [] (Class_A& obj) { obj.somethingToDo(); obj.moreThingsToDo(); };
}
Upvotes: 4
Reputation: 706
Yes this program is likely to have problems. C++ does not protect you from invalidating pointers, and as you've highlighted the objects in your vector will potentially move address when the vector resizes, which will cause problems if you try to run your lambda.
You will probably be unable to compile the program without capturing this
. You will also end up with issues if you try to capture references or pointers to any part of your object without being sure the memory being pointed at will not move.
It pays to be cautious, as a program like this is not guaranteed to crash even if you have a bug, as the old data may still exist in memory even when your vector resizes. So if you try capturing this
and don't see any issues at runtime it does not mean that your program is correct.
For a straight forward solution, I'd look at allocating your objects on the heap using one of the smart pointer types such as std::unique_ptr
or std::shared_ptr
.
Upvotes: 3
Reputation: 20619
If copying the object is a possibility, then you can capture *this
by value: (requires C++17)
return [*this] { somethingToDo(); moreThingsToDo(); }
This copies the whole object into the closure to avoid out-of-lifetime access to the original object.
Upvotes: 3