Spagnol
Spagnol

Reputation: 43

C++ Lambda stored in container capturing "this"

Consider the following minimal code:

#include <functional>
#include <iostream>
#include <string>
#include <vector>

class A
{
public:
  A() :
    s("dummy"),
    f([&, this]{ return s == "dummy"; }),
  {}
  std::string s;
  std::function<bool ()> f;
};

int main(int argc, char *argv[])
{
  A a;
  a.f(); // seems fine

  std::vector<A> as({});
  as[0].f(); // segmentation fault
}

Class A has a lambda member that captures this pointer. When running the code above, lambda works fine when invoked from standalone A instance, but I get segmentation fault when called from an instance stored in the vector.

Why does it happen?

Upvotes: 3

Views: 804

Answers (1)

Praetorian
Praetorian

Reputation: 109119

There are a couple of different problems with your example. This line

std::vector<A> as({});

constructs an empty vector. When you index into the first element on the next line, you have undefined behavior, and your program crashes in this case.

If you change that to

std::vector<A> as;
as.emplace_back();
a[0].f();

the code will work as you expect, but that doesn't mean there are no more problems. If you add a few more elements to your vector, it'll reallocate storage to accommodate the new elements, and the previously stored lambdas will get move constructed. But when that happens, they will still retain their old this pointer values, which point to objects that are now dead. Invoking the lambda after that is undefined behavior.

Here's an example of the potential problems

class A
{
public:
  A() :
    s("dummy"),
    f([&, this]{ std::cout << this << '\n'; return s == "dummy"; })
  {}
  std::string s;
  std::function<bool ()> f;
};

int main()
{
  std::vector<A> as;
  {
    A a;
    a.f();
    as.push_back(a);  // make a copy of a
    as[0].f();        // this pointer stays the same as the original
  } // lifetime of a ends

  as[0].f();  // undefined behavior
}

Live demo

Upvotes: 9

Related Questions