frankelot
frankelot

Reputation: 14409

Lambda capture scope

I'm struggling to understand the scope and ownership of lambda captures, why does the following code work?:

#include <iostream>

struct Foo {
    void setCallback(std::function<void()> f) { callback = std::move(f); }

    void trigger() { callback(); }

private:
    std::function<void()> callback;
};

struct Bar {
    int i;

    Bar(int i) : i{i} { std::cout << "bar constructed" << std::endl; }

    ~Bar() { std::cout << "bar destroyed" << std::endl; }
};

int main() {
    Foo foo{};

    { // scope
        Bar bar{1};
        foo.setCallback([&bar]() { std::cout << bar.i << std::endl; });
        bar.i = 2;
    } //end of scope, bar gets deleted

    foo.trigger();
    return 0;
}

Output:

bar constructed 
bar destroyed
2

Is the compiler inlining bar.i in the lambda function statically? I'm capturing by reference, what is going on?

Upvotes: 1

Views: 255

Answers (1)

Jean-Marc Volle
Jean-Marc Volle

Reputation: 3323

Hello Your code does not work because bar is descoped before the lambda that captured it is executed. The lambda uses a dangling reference. Here is an updated sample that will convince you, that if it works in your example it is simply because nothing else has been allocated at the address where bar was initially stored.

#include <iostream>
#include <functional>

struct Foo {
    void setCallback(std::function<void()> f) { callback = std::move(f); }

    void trigger() { callback(); }

private:
    std::function<void()> callback;
};

struct Bar {
    int i;

    Bar(int i) : i{i} { std::cout << "bar constructed" << std::endl; }

    ~Bar() { std::cout << "bar destroyed" << std::endl; }
};

int main() {
    Foo foo{};

    { // scope
        Bar bar{1};
        foo.setCallback([&bar]() { std::cout << bar.i << std::endl; });
        bar.i = 2;
    } //end of scope, bar gets deleted
    
    volatile int  a = 5;  // allocate something after bar is destroyed
    
    foo.trigger();
    return 0;
}

Now you will get:

bar constructed 
bar destroyed
5

5 is the next value allocated on the stack in place of bar.

Upvotes: 2

Related Questions