Why value captured by reference in lambda is broke

2019-07-14 05:52发布

问题:

This question already has an answer here:

  • Can a local variable's memory be accessed outside its scope? 20 answers

Repeatable example:

#include <iostream>
#include <boost/asio/io_service.hpp>

boost::asio::io_service io_service;

void test1(int t_a)
{
    std::cout << "in test1: t_a = " << t_a << std::endl;
}

void test2(int t_a)
{
    std::cout << "in test2: t_a = " << t_a << std::endl;

    io_service.post([&t_a]()
    {
        std::cout << "in test2 post lambda: t_a = " << t_a << std::endl;
        test1(t_a);
    });
}

int main(int, char**)
{
    int a = 42;

    for (;;) {
        try
        {
            test2(a);
            io_service.run();
            break;
        }
        catch (std::exception & e)
        {

        }
    }
}

Output:

in test2: t_a = 42
in test2 post lambda: t_a = 16451253
in test1: t_a = 16451253
Press any key to continue . . .

Why is that? Capturing by value is working as I expect it to, but why does capturing by reference behave like this?

Note int here is only for example, consider any big object which is bad to pass by value (expendable copy, for example)

Why if I declare test1 and test2 as test1(const int& t_a) and test2(const int& t_a) all is working correctly then?

回答1:

The reference of t_a is valid only in void test2(int t_a) scope. Capture by value in your case.



回答2:

Unfortunately C++ doesn't provide a garbage collector (yet) and therefore the use of closures is somewhat impaired.

You can capture by reference (thus for example having multiple closures referencing the same captured object) but the lifetime for the object must be guaranteed independently from the lifetime of the closures; in other words if a lambda capturing a variable by reference survives the referenced object and is called when the object has already been destroyed then you get in the usual "undefined behavior" realm.

This is what happens in your code: the captured variable is a parameter of the function and when the closure is called it has already been destroyed.

A solution is either capture by value (in this case the capture object is copied inside the closure and you don't have a lifetime problem) or use for example a reference-counted smart pointer like std::shared_ptr to a free-store allocated object to ensure that as long as the closure survives the referenced (pointed-to) object also survives.



回答3:

Because the reference is dangling. It refers to a function argument, which will cease to exist as soon as the function returns. The function returns before your asynchronous code runs.

Just capture t_a by value, surely?