在Lambda中移动捕获

如何在C++11 Lambda中通过移动(也称为右值引用)捕获?

我试着写这样的东西:

std::unique_ptr<int> myPointer(new int);


std::function<void(void)> example = [std::move(myPointer)]{
*myPointer = 4;
};
108422 次浏览

You could also use std::bind to capture the unique_ptr:

std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);

Generalized lambda capture in C++14

In C++14 we will have the so called generalized lambda capture. This enables move capture. The following will be legal code in C++14:

using namespace std;


// a unique_ptr is move-only
auto u = make_unique<some_type>( some, parameters );


// move the unique_ptr into the lambda
go.run( [ u = move(u) ] { do_something_with( u ); } );

Also note if you need to move object from lambda to some other function you need to make lambda mutable.

go.run( [ u = move(u) ] mutable { do_something_with( std::move(u) ); } );

The generalized lambda capture is much more general in the sense that captured variables can be initialized with anything like so:

auto lambda = [value = 0] mutable { return ++value; };

In C++11 this is not possible yet, but with some tricks that involve helper types. Fortunately, the Clang 3.4 compiler already implements this awesome feature. The compiler will be released December 2013 or January 2014, if the recent release pace will be kept.

UPDATE: The Clang 3.4 compiler was released on 6 Jan 2014 with the said feature.

A workaround for move capture

Here's an implementation of a helper function make_rref which helps with artificial move capture

#include <cassert>
#include <memory>
#include <utility>


template <typename T>
struct rref_impl
{
rref_impl() = delete;
rref_impl( T && x ) : x{std::move(x)} {}
rref_impl( rref_impl & other )
: x{std::move(other.x)}, isCopied{true}
{
assert( other.isCopied == false );
}
rref_impl( rref_impl && other )
: x{std::move(other.x)}, isCopied{std::move(other.isCopied)}
{
}
rref_impl & operator=( rref_impl other ) = delete;
T && move()
{
return std::move(x);
}


private:
T x;
bool isCopied = false;
};


template<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ std::move(x) };
}

And here's a test case for that function that ran successfully on my gcc 4.7.3.

int main()
{
std::unique_ptr<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert(  lambda() );
assert( !lambda() );
}

The drawback here is that lambda is copyable and when copied the assertion in the copy constructor of rref_impl fails leading to a runtime bug. The following might be a better and even more generic solution because the compiler will catch the error.

Emulating generalized lambda capture in C++11

Here's one more idea, on how to implement generalized lambda capture. The use of the function capture() (whose implementation is found further down) is as follows:

#include <cassert>
#include <memory>


int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert(  lambda() );
assert( !lambda() );
}

Here lambda is a functor object (almost a real lambda) which has captured std::move(p) as it is passed to capture(). The second argument of capture is a lambda which takes the captured variable as an argument. When lambda is used as a function object, then all arguments that are passed to it will be forwarded to the internal lambda as arguments after the captured variable. (In our case there are no further arguments to be forwarded). Essentially, the same as in the previous solution happens. Here's how capture is implemented:

#include <utility>


template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}


template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}


template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};


template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}

This second solution is also cleaner, because it disables copying the lambda, if the captured type is not copyable. In the first solution that can only be checked at runtime with an assert().

You can achieve most of what you want using std::bind, like this:

std::unique_ptr<int> myPointer(new int{42});


auto lambda = std::bind([](std::unique_ptr<int>& myPointerArg){
*myPointerArg = 4;
myPointerArg.reset(new int{237});
}, std::move(myPointer));

The trick here is that instead of capturing your move-only object in the captures list, we make it an argument and then use partial application via std::bind to make it vanish. Note that the lambda takes it by reference, because it's actually stored in the bind object. I also added code that writes to the actual movable object, because that's something you might want to do.

In C++14, you can use generalized lambda capture to achieve the same ends, with this code:

std::unique_ptr<int> myPointer(new int{42});


auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};

But this code doesn't buy you anything you didn't have in C++11 via std::bind. (There are some situations where generalized lambda capture is more powerful, but not in this case.)

Now there is just one problem; you wanted to put this function in a std::function, but that class requires that the function be CopyConstructible, but it isn't, it's only MoveConstructible because it's storing a std::unique_ptr which isn't CopyConstructible.

You to work around the issue with wrapper class and another level of indirection, but perhaps you don't need std::function at all. Depending on your needs, you may be able to use std::packaged_task; it'd do the same job as std::function, but it doesn't require the function to be copyable, only movable (similarly, std::packaged_task is only movable). The downside is that because it's intended to be used in conjunction with std::future, you can only call it once.

Here's a short program that shows all of these concepts.

#include <functional>   // for std::bind
#include <memory>       // for std::unique_ptr
#include <utility>      // for std::move
#include <future>       // for std::packaged_task
#include <iostream>     // printing
#include <type_traits>  // for std::result_of
#include <cstddef>


void showPtr(const char* name, const std::unique_ptr<size_t>& ptr)
{
std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = "
<< ptr.get();
if (ptr)
std::cout << ", *" << name << " = " << *ptr;
std::cout << std::endl;
}


// If you must use std::function, but your function is MoveConstructable
// but not CopyConstructable, you can wrap it in a shared pointer.
template <typename F>
class shared_function : public std::shared_ptr<F> {
public:
using std::shared_ptr<F>::shared_ptr;


template <typename ...Args>
auto operator()(Args&&...args) const
-> typename std::result_of<F(Args...)>::type
{
return (*(this->get()))(std::forward<Args>(args)...);
}
};


template <typename F>
shared_function<F> make_shared_fn(F&& f)
{
return shared_function<F>{
new typename std::remove_reference<F>::type{std::forward<F>(f)}};
}




int main()
{
std::unique_ptr<size_t> myPointer(new size_t{42});
showPtr("myPointer", myPointer);
std::cout << "Creating lambda\n";


#if __cplusplus == 201103L // C++ 11


// Use std::bind
auto lambda = std::bind([](std::unique_ptr<size_t>& myPointerArg){
showPtr("myPointerArg", myPointerArg);
*myPointerArg *= 56;                    // Reads our movable thing
showPtr("myPointerArg", myPointerArg);
myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it
showPtr("myPointerArg", myPointerArg);
}, std::move(myPointer));


#elif __cplusplus > 201103L // C++14


// Use generalized capture
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
showPtr("myPointerCapture", myPointerCapture);
*myPointerCapture *= 56;
showPtr("myPointerCapture", myPointerCapture);
myPointerCapture.reset(new size_t{*myPointerCapture * 237});
showPtr("myPointerCapture", myPointerCapture);
};


#else
#error We need C++11
#endif


showPtr("myPointer", myPointer);
std::cout << "#1: lambda()\n";
lambda();
std::cout << "#2: lambda()\n";
lambda();
std::cout << "#3: lambda()\n";
lambda();


#if ONLY_NEED_TO_CALL_ONCE
// In some situations, std::packaged_task is an alternative to
// std::function, e.g., if you only plan to call it once.  Otherwise
// you need to write your own wrapper to handle move-only function.
std::cout << "Moving to std::packaged_task\n";
std::packaged_task<void()> f{std::move(lambda)};
std::cout << "#4: f()\n";
f();
#else
// Otherwise, we need to turn our move-only function into one that can
// be copied freely.  There is no guarantee that it'll only be copied
// once, so we resort to using a shared pointer.
std::cout << "Moving to std::function\n";
std::function<void()> f{make_shared_fn(std::move(lambda))};
std::cout << "#4: f()\n";
f();
std::cout << "#5: f()\n";
f();
std::cout << "#6: f()\n";
f();
#endif
}

I've put a the above program on Coliru, so you can run and play with the code.

Here's some typical output...

- &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42
Creating lambda
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x0
#1: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
#2: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
#3: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
Moving to std::function
#4: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
#5: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
#6: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536

You get to see heap locations being reused, showing that the std::unique_ptr is working properly. You also see the function itself move around when we stash it in a wrapper we feed to std::function.

If we switch to using std::packaged_task, it the last part becomes

Moving to std::packaged_task
#4: f()
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608

so we see that the function has been moved, but rather than getting moved onto the heap, it's inside the std::packaged_task that's on the stack.

Hope this helps!

I was looking at these answers, but I found bind hard to read and understand. So what I did was make a class that moved on copy instead. In this way, it is explicit with what it is doing.

#include <iostream>
#include <memory>
#include <utility>
#include <type_traits>
#include <functional>


namespace detail
{
enum selection_enabler { enabled };
}


#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \
= ::detail::enabled


// This allows forwarding an object using the copy constructor
template <typename T>
struct move_with_copy_ctor
{
// forwarding constructor
template <typename T2
// Disable constructor for it's own type, since it would
// conflict with the copy constructor.
, ENABLE_IF(
!std::is_same<std::remove_reference_t<T2>, move_with_copy_ctor>::value
)
>
move_with_copy_ctor(T2&& object)
: wrapped_object(std::forward<T2>(object))
{
}


// move object to wrapped_object
move_with_copy_ctor(T&& object)
: wrapped_object(std::move(object))
{
}


// Copy constructor being used as move constructor.
move_with_copy_ctor(move_with_copy_ctor const& object)
{
std::swap(wrapped_object, const_cast<move_with_copy_ctor&>(object).wrapped_object);
}


// access to wrapped object
T& operator()() { return wrapped_object; }


private:
T wrapped_object;
};




template <typename T>
move_with_copy_ctor<T> make_movable(T&& object)
{
return{ std::forward<T>(object) };
}


auto fn1()
{
std::unique_ptr<int, std::function<void(int*)>> x(new int(1)
, [](int * x)
{
std::cout << "Destroying " << x << std::endl;
delete x;
});
return [y = make_movable(std::move(x))]() mutable {
std::cout << "value: " << *y() << std::endl;
return;
};
}


int main()
{
{
auto x = fn1();
x();
std::cout << "object still not deleted\n";
x();
}
std::cout << "object was deleted\n";
}

The move_with_copy_ctor class and it's helper function make_movable() will work with any movable but not copyable object. To get access to the wrapped object, use the operator()().

Expected output:

value: 1
object still not deleted
value: 1
Destroying 000000DFDD172280
object was deleted

Well, the pointer address may vary. ;)

Demo

Late, but as some people (including me) are still stuck on c++11:

To be honest, I don't really like any of the posted solutions. I'm sure they will work, but they require a lot of additional stuff and/or cryptical std::bind syntax... and I don't think that it's worth the effort for such a temporary solution which will be refactored anyway when upgrading to c++ >= 14. So I think the best solution is to avoid move capturing for c++11 completely.

Usually the simplest and best readable solution is to use std::shared_ptr, which are copyable and so the move is completely avoidable. Downside is, that it's a little less efficient, but in many cases efficiency is not so important.

// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);


// convert/move the unique ptr into a shared ptr
std::shared_ptr<int> mySharedPointer( std::move(myPointer) );


std::function<void(void)> = [mySharedPointer](){
*mySharedPointer = 4;
};


// at end of scope the original mySharedPointer is destroyed,
// but the copy still lives in the lambda capture.

.

If the very rare case occurs, that it's really mandatory to move the pointer (e.g. you want to explicitly delete a pointer in a separate thread due to lengthy delete duration, or performance is absolutely crucial), that's pretty much the only case where I still use raw pointers in c++11. These are of course also copyable.

Usually I mark these rare cases with a //FIXME: to ensure that it's refactored once upgrading to c++ 14.

// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);


//FIXME:c++11 upgrade to new move capture on c++>=14


// "move" the pointer into a raw pointer
int* myRawPointer = myPointer.release();


// capture the raw pointer as a copy.
std::function<void(void)> = [myRawPointer](){
std::unique_ptr<int> capturedPointer(myRawPointer);
*capturedPointer = 4;
};


// ensure that the pointer's value is not accessible anymore after capturing
myRawPointer = nullptr;

Yes, raw pointers are pretty frowned upon these days (and not without reason), but I really think in these rare (and temporary!) cases they are the best solution.

This seems to work on gcc4.8

#include <memory>
#include <iostream>


struct Foo {};


void bar(std::unique_ptr<Foo> p) {
std::cout << "bar\n";
}


int main() {
std::unique_ptr<Foo> p(new Foo);
auto f = [ptr = std::move(p)]() mutable {
bar(std::move(ptr));
};
f();
return 0;
}