Timothy Shields
Timothy Shields

Reputation: 79441

Lambda expression sneakily capturing `this`

Suppose I have the following.

struct A
{
    int x;
    std::function<int()> f1() { return [=](){ return x; }; }
    std::function<int()> f2() { return [=](){ return this->x; }; }
    std::function<int()> f3() { return [this](){ return x; }; }
    std::function<int()> f4() { return [this](){ return this->x; }; }
    std::function<int()> f5()
    {
        int temp = x;
        return [=](){ return temp; };
    }
}

And now I have the following code.

auto a = std::make_shared<A>();
a->x = 5;
std::function<int()> f = a.f#();
a.reset();
int x = f();

where the f# is referring to any of f1, f2, f3, f4, f5.

These functions are exhibiting behavior in one of two sets:

  1. return 5 when called (f5), or
  2. crashed trying to dereference nullptr (f1, f2, f3, f4).

I understand that this is because some are capturing "this" in the member function of A, either implicitly or explicitly.

What is the formal rule that's determining behavior 1 or 2?

I spent a while dealing with a bug that was caused by something similar to f1, thinking it would capture x and never considering it would capture this, so I figured it would be useful to get this documented.

Upvotes: 7

Views: 269

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473272

There is no formal rule that determines this behavior. Because this behavior is undefined.

Your lambdas are accessing an object that doesn't exist. You can't capture a member variable directly by value; you always capture them by this. Which means you're capturing them by reference. Once the object is deleted, any attempt to access that deleted object results in undefined behavior.

The exception to this is f5, which should return a consistent value, guaranteed. It's completely disconnected from the originating object.

Upvotes: 8

Related Questions