Excerpt from Effective Modern C++ Chapter 6
Example
1 |
|
Use Init Capture
Default capture modes are not recommended in lambda.
For problems with by-reference capture(&), if lambda captures data in a local function, exiting the local function would make the data captured undefined. Long-term, it’s simply better software engineering to explicitly list the local variables and parameters that a lambda depends on.
For problems with by-value capture(=), if you use a raw pointer, and capture a pointer by value, but the pointer is later deleted outside of lambda, it is very dangerous. Another scenario is that captures only apply to non-static local variables, so you cannot capture a data member of a class by value. For example:
1 |
|
In the above example, line A actually captures the this pointer of the Example class. This means the Example object must exist when lambda is invoked. If not, a danling pointer is held in filters1. Line B and C simply won’t compile, since no local divisor can be captured.
Also, since by value capture does not capture static variables, the captured value may seem self-contained but actually is global, which may result in unexpected behaviors. Example:
1 |
|
The recommended way is to use init capture.
1 |
|
For [pw = std::move(pw)]
, pw
in the left refers to the scope inside the closure class, pw
in std::move(pw)
refers to the object declared above the lambda in the local scope.
Comparison with std::bind
In C++, objects can’t be move constructed using lambda, but can be emulated by using std::bind. std::bind produces function objects, which contain copies of all the arguments(copy or move constructed) passed to std::bind. Since the bind objects store copies of arguments, the lifetime of the bind object is the same as that of the closure.
Generally speaking, lambdas are preferable over std::bind, for the following reasons:
- Lambdas are much more readable
- Arguments passed into bind will be evaluated when bind object is created, not when the function is invoked, which may result in unexpected behaviors
- When overloading a function with the same name, the function needs to be cast to a proper function pointer type in order to compile, which makes compilers less likely to inline function calls
Perfect forwarding rules
To use universal reference with lambda, use the following:
1 |
|
When param is an lvalue reference, decltype(param) produces lvalue type. When param is an rvalue reference, decltype(param) produces rvalue type. In C++ implementation, std::forward looks likes this:
1 |
|
According to the reference collapsing rule: If either reference is an lvalue reference, the result is an lvalue reference. Otherwise (i.e., if both are rvalue references) the result is an rvalue reference. Therefore, whatever type we passed in for param in the lambda, it will produce the correct type.