2

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?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
vladon
  • 8,158
  • 2
  • 47
  • 91
  • 5
    You are using the captured value outside its lifetime, you can find a similar example [here](http://stackoverflow.com/q/28506342/1708801) – Shafik Yaghmour Sep 14 '15 at 18:57

3 Answers3

4

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Or `test2` also takes it argument by reference, and then the OP needs to ensure the lifetime of whatever is passed to `test2` exceeds that of the asio call. – Praetorian Sep 14 '15 at 19:04
  • 1
    What do you mean by `test2(int t_a)` scope? I think the function call `io_service.post` inside the body of `test2()` is within the scope of function `test2`. – Allanqunzi Sep 14 '15 at 19:25
  • 1
    `io_service.post(..)` registers the function, whereas `io_service.run()` call it. – Jarod42 Sep 14 '15 at 19:54
1

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.

6502
  • 112,025
  • 15
  • 165
  • 265
  • One another moment: why if I declare `test1` and `test2` as `test1(const int& t_a)` and `test2(const int& t_a)` all is working correctly then? – vladon Sep 14 '15 at 19:10
  • @vladon: Pure chance, effectively. Well, depending on what you bind those references to. We have no idea what the referent's lifetime is. – Lightness Races in Orbit Sep 14 '15 at 19:14
  • The reason is that in this case the variable captured by reference is in `main` and remains alive enough. – 6502 Sep 14 '15 at 19:16
1

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?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055