Home > Back-end >  C lambda, which was specified to capture by value, actually behaves as if it captures by reference
C lambda, which was specified to capture by value, actually behaves as if it captures by reference

Time:10-09

Looking at various examples with lambda expressions, I came across unexpected behavior for me. In this code, I expect that the variables captured by value will not change inside the lambda. Code execution shows the opposite. Can someone give an explanation. Thanks.

#include <iostream>
#include <functional>

using namespace std;

class Fuu{

public:

    Fuu(int v):m_property(v){}

    void setProperty(int v ){m_property = v;}

    std::function<void(int)> lmbSet {[=](int v){  m_property = v;}};
    std::function<void(char *)> lmbGet {[=](char * v){ cout<< v << " "<< m_property << endl;}};
    std::function<void()> lmb4Static {[=]{  cout<<"static: "<< s_fild << endl;}};

    void call() const {
        lmbGet("test") ;
    }

    static int s_fild ;

    int m_property;
};

int Fuu::s_fild = 10;

int main()
{
    Fuu obj(3);

    obj.call();

    obj.setProperty(5);

    obj.call();//expect 3

    obj.lmbSet(12);

    obj.call(); //expect 3

    obj.lmb4Static();

    Fuu::s_fild = 11;

    obj.lmb4Static(); //expect 10

    return 0;
}

CodePudding user response:

That is a common problem. Common enough that they deprecated the implicit capture of this in a lambda with = for C 20. When your code is doing is:

std::function<void(int)> lmbSet {[=](int v){  m_property = v;}};

It is effectively doing this:

std::function<void(int)> lmbSet {[this](int v){  this->m_property = v;}};

The lambda captures the this pointer by copy, not m_property at all.

In general, but formally in C 20, the preferred way to do this would be to explicitly copy this (in which case, no = is necessary, though you could):

std::function<void(int)> lmbSet {[this](int v){  m_property = v;}};

To make a copy of the current object and modify that copy, you'd need to dereference this (and then the capture is a copy of *this) and to modify it, you'd need to mark your lambda mutable:

std::function<void(int)> lmbSet {[*this](int v) mutable {  m_property = v;}};

It doesn't seem very useful, but perhaps that helps explain what's going on.

[Note: it's not valid to copy *this until the construction completes, or it's copying an object before its lifetime starts, and that will result in undefined behavior.]

CodePudding user response:

Capture by value only takes copies of local variables (none in your example) and this pointer. Capturing this by value is equivalent to capturing non-static data members by reference. Static member variables, like globals, are not captured at all - you are accessing them directly instead.

If you actually need to keep a copy of non-local data, you can do it using "generalized capture" syntax which was added in C 14:

[m_property=m_property](char * v){ cout<< v << " "<< m_property << endl;}

CodePudding user response:

In this case [=] effectively just copies this as a pointer. Then, when you access data members in your closure the copy of this is dereferenced, and if you modify that member it modifies the original member. If you think about it, it isn't possible that [=] copies each member. You would have an infinite recursion of copies as each lmbSet would make a new copy of lmbGet, which in turn would make a new copy of lmbSet.

Note that this solution requires that the rule of 3/5/0 be applied. If you try to make a copy of an instance of Fuu, the captured this pointer will copied and point to the original instance and not to the new copy. In general, capturing this or pointers to data members in default member initializes should be avoided.

  • Related