11

What are the pitfalls of using lambda default capture by value ([=]) or by reference ([&]) in C++11?

I know some pitfalls like:

  • If the lifetime of a closure created from the lambda exceeds the lifetime of the local variable, the reference in the closure will be dangling?

Does default capturing by value have any disadvantages?

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
Ajay yadav
  • 4,141
  • 4
  • 31
  • 40

4 Answers4

12

I think the dangling reference problem you mentioned is the main pitfall.

One other thing that is sometimes overlooked however, is that even when one uses a capture-by-value-lambda in a member function, it doesn't create a copy of the used member variables, but only makes a copy of the this pointer.

For one, this means that you are again open to the dangling pointer Problem and second, you might accidentally modify variables outside of the scope of the lambda, even when it looks like, you are only modifying a local copy.

E.g. this will print 0 1 1 instead of 0 1 0

struct Foo {
    int bar=0;
    int bas() {
        auto inc = [=]() {          
            bar++;  //this is equivalent to this->bar++ 
            return bar; 
        };
        return inc();
    }
};

int main() {
    Foo foo;
    std::cout << foo.bar <<" ";
    std::cout << foo.bas() << " ";
    std::cout << foo.bar << std::endl; 
}

EDIT: Just to avoid confusion related to the point made by @Snps:
If bar was a local variable in bas() (and thus be captured by value), the above lambda would not compile, as by-value-captured-variables are by default const, unless you explicitly specify the lambda as mutable. So if you think about it, it is clear that bar is not copied, but that's easily forgotten, when reading or writing code.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • Hi Mike, Thank you answering, as per my understanding capturing value type become data member of closure class, reference will not become data member of closure class. Please confirm. – Ajay yadav Jan 03 '16 at 03:07
  • @Ajayyadav: Well, it also becomes a data member, but of reference type – MikeMB Jan 03 '16 at 10:35
5

It has exactly the same advantages and disadvantage as a comparison between:

int value(const T x) { ... }
int value(T& x) { ... }
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • 1
    Good answer, I would just add that lambdas are objects. Therefore when capturing by value, repeated usage of lambda object will use it's internal state. That is quite different from usage of function like `int value(const T x) { ... }`. For reference see - http://stackoverflow.com/questions/5501959/why-does-c0xs-lambda-require-mutable-keyword-for-capture-by-value-by-defau – Jendas Jan 02 '16 at 16:58
  • @Jendas: I'm not sure that your reference is correct, but you are correct that the value are captured at lambda creation time, instead of at call time. – Bill Lynch Jan 02 '16 at 17:04
  • Hi Bill, Thank you for answering, by default value type are captured as const and reference type non const – Ajay yadav Jan 02 '16 at 17:11
  • Yes, it doesn't address the issue exactly, but it is the closest information about lambdas internal state I've found. – Jendas Jan 02 '16 at 17:11
5

Capturing by value using [=] or [<identifier>] has the effect of creating a lambda member of the exact same type as the captured entity, including constness, e.g., when capturing a const int by value, the resulting member can not be mutated even when the lambda call operator is mutable.

const int i = 1;
[=] () mutable { ++i; }(); // Error: increment of read-only variable.

This can be worked around using C++14's initializing capture expressions:

[i = i] () mutable { ++i; }(); // Ok
Felix Glas
  • 15,065
  • 7
  • 53
  • 82
2

Capturing by values involve copying the closed values, so might mean more memory consumption and more processing for that copy.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547