Home > other >  Do objects captured by a lambda exist for as long as the lambda?
Do objects captured by a lambda exist for as long as the lambda?

Time:01-10

I have always assumed lambda were just function pointers, but I've never thought to use capture statements seriously...

If I create a lambda that captures by copy, and then move that lambda to a completely different thread and make no attempt to save the original objects used in the lambda, will it retain those copies for me?

std::thread createThread() {
    std::string str("Success");
    auto func = [=](){ 
        printf("%s", str.c_str());
    };
    str = "Failure";
    return std::thread(func);
}

int main() {
    std::thread thread = createThread();
    thread.join();  
    // assuming the thread doesn't execute anything until here...
    // would it print "Success", "Failure", or deference a dangling pointer?
    return 0;
}

CodePudding user response:

It is guaranteed to print Success. Capture-by-copy does exactly what it says. It make a copy of the object right there and stores this copy as part of the closure object. The member of the closure object created from the capture lives as long as the closure object itself.

A lambda is not a function pointer. Lambdas are general function objects that can have internal state, which a function pointer can't have. In fact, only capture-less lambdas can be converted to function pointers and so may behave like one sometimes.

The lambda expression produces a closure type that basically looks something like this:

struct /*unnamed1*/ {
    /*unnamed1*/(const /*unnamed1*/&) = default;  
    /*unnamed1*/(/*unnamed1*/&&) = default;           

    /*unnamed1*/& operator=(const /*unnamed1*/&) = delete;       

    void operator()() const {
        printf("%s", /*unnamed2*/.c_str());
    };

    std::string /*unnamed2*/;  
};

and the lambda expression produces an object of this type, with /*unnamed2*/ direct-initialized to the current value of str. (Direct-initialized meaning as if by std::string /*unnamed2*/(str);)

CodePudding user response:

You have 3 situations

  1. You can be design guarantee that variables live longer then the thread, because you synchronize with the end of the thread before variables go out of scope.
  2. You know your thread may outlive the scope/life cycle of your thread but you don't need access to the variables anymore from any other thread.
  3. You can't say which thread lives longest, you have multiple thread accessing your data and you want to extend the live time of your variables

In case 1. Capture by reference

In case 2. Capture by value (or you even use move) variables

In case 3. Make data shared, std::shared_ptr and capture that by value

Case 3 will extend the lifetime of the data to the lifetime of the longest living thread.

Note I prefer using std::async over std::thread, since that returns a RAII object (a future). The destructor of that will synchronize with the thread. So you can use that as members in objects with a thread and make sure the object destruction waits for the thread to finish.

  • Related