// Item 21 for info on
// std::make_unique
… // configure *pw
auto func = [ pw = std::move(pw)] // init data mbr
{ return pw->isValidated() // in closure w/
&& pw->isArchived(); }; // std::move(pw)
The highlighted text comprises the init capture. To the left of the “ =
” is the name of the data member in the closure class you're specifying, and to the right is the initializing expression. Interestingly, the scope on the left of the “ =
” is different from the scope on the right. The scope on the left is that of the closure class. The scope on the right is the same as where the lambda is being defined. In the example above, the name pw
on the left of the “ =
” refers to a data member in the closure class, while the name pw
on the right refers to the object declared above the lambda, i.e., the variable initialized by the call to std::make_unique
. So “ pw = std::move(pw)
” means “create a data member pw
in the closure, and initialize that data member with the result of applying std::move
to the local variable pw
.”
As usual, code in the body of the lambda is in the scope of the closure class, so uses of pw
there refer to the closure class data member.
The comment “configure *pw
” in this example indicates that after the Widget
is created by std::make_unique
and before the std::unique_ptr
to that Widget
is captured by the lambda, the Widget
is modified in some way. If no such configuration is necessary, i.e., if the Widget
created by std::make_unique
is in a state suitable to be captured by the lambda, the local variable pw
is unnecessary, because the closure class's data member can be directly initialized by std::make_unique
:
auto func = [ pw = std::make_unique()] // init data mbr
{ return pw->isValidated() // in closure w/
&& pw->isArchived(); }; // result of call
// to make_unique
This should make clear that the C++14 notion of “capture” is considerably generalized from C++11, because in C++11, it's not possible to capture the result of an expression. As a result, another name for init capture is generalized lambda capture .
But what if one or more of the compilers you use lacks support for C++14's init capture? How can you accomplish move capture in a language lacking support for move capture?
Remember that a lambda expression is simply a way to cause a class to be generated and an object of that type to be created. There is nothing you can do with a lambda that you can't do by hand. The example C++14 code we just saw, for example, can be written in C++11 like this:
class IsValAndArch { // "is validated
public: // and archived"
using DataType = std::unique_ptr;
explicit IsValAndArch(DataType&& ptr) // Item 25 explains
: pw(std::move(ptr)) {} // use of std::move
bool operator()() const
{ return pw->isValidated() && pw->isArchived(); }
private:
DataType pw;
};
auto func = IsValAndArch(std::make_unique());
That's more work than writing the lambda, but it doesn't change the fact that if you want a class in C++11 that supports move-initialization of its data members, the only thing between you and your desire is a bit of time with your keyboard.
If you want to stick with lambdas (and given their convenience, you probably do), move capture can be emulated in C++11 by
1. moving the object to be captured into a function object produced by std::bind
and
2. giving the lambda a reference to the “captured” object.
If you're familiar with std::bind
, the code is pretty straightforward. If you're not familiar with std::bind
, the code takes a little getting used to, but it's worth the trouble.
Suppose you'd like to create a local std::vector
, put an appropriate set of values into it, then move it into a closure. In C++14, this is easy:
std::vector data; // object to be moved
// into closure
… // populate data
auto func = [data = std::move(data)] // C++14 init capture
{ /* uses of data */ };
I've highlighted key parts of this code: the type of object you want to move ( std::vector
), the name of that object ( data
), and the initializing expression for the init capture ( std::move(data)
). The C++11 equivalent is as follows, where I've highlighted the same key things:
std::vector data; // as above
… // as above
auto func =
std::bind( // C++11 emulation
[]( const std::vector& data) // of init capture
{ /* uses of data */ },
std::move(data)
);
Like lambda expressions, std::bind
produces function objects. I call function objects returned by std::bind
bind objects . The first argument to std::bind
is a callable object. Subsequent arguments represent values to be passed to that object.
A bind object contains copies of all the arguments passed to std::bind
. For each lvalue argument, the corresponding object in the bind object is copy constructed. For each rvalue, it's move constructed. In this example, the second argument is an rvalue (the result of std::move
— see Item 23), so data
is move constructed into the bind object. This move construction is the crux of move capture emulation, because moving an rvalue into a bind object is how we work around the inability to move an rvalue into a C++11 closure.
When a bind object is “called” (i.e., its function call operator is invoked) the arguments it stores are passed to the callable object originally passed to std::bind
. In this example, that means that when func
(the bind object) is called, the move-constructed copy of data inside func
is passed as an argument to the lambda that was passed to std::bind
.
This lambda is the same as the lambda we'd use in C++14, except a parameter, data
, has been added to correspond to our pseudo-move-captured object. This parameter is an lvalue reference to the copy of data
in the bind object. (It's not an rvalue reference, because although the expression used to initialize the copy of data
(“ std::move(data)
”) is an rvalue, the copy of data
itself is an lvalue.) Uses of data
inside the lambda will thus operate on the move-constructed copy of data inside the bind object.
By default, the operator()
member function inside the closure class generated from a lambda is const
. That has the effect of rendering all data members in the closure const
within the body of the lambda. The move-constructed copy of data
inside the bind object is not const
, however, so to prevent that copy of data
from being modified inside the lambda, the lambda's parameter is declared reference-to- const
. If the lambda were declared mutable
, operator()
in its closure class would not be declared const
, and it would be appropriate to omit const
in the lambda's parameter declaration:
Читать дальше