How to capture a unique_ptr into a lambda expression?
C++LambdaC++11Unique PtrC++ Problem Overview
I have tried the following:
std::function<void ()> getAction(std::unique_ptr<MyClass> &&psomething){
//The caller given ownership of psomething
return [psomething](){
psomething->do_some_thing();
//psomething is expected to be released after this point
};
}
But it does not compile. Any ideas?
UPDATE:
AS suggested, some new syntax is required to explicitly specify we need to transfer the ownership to the lambda, I am now thinking about the following syntax:
std::function<void ()> getAction(std::unique_ptr<MyClass> psomething){
//The caller given ownership of psomething
return [auto psomething=move(psomething)](){
psomething->do_some_thing();
//psomething is expected to be released after this point
};
}
Would it be a good candidate?
UPDATE 1:
I will show my implementation of move
and copy
as following:
template<typename T>
T copy(const T &t) {
return t;
}
//process lvalue references
template<typename T>
T move(T &t) {
return std::move(t);
}
class A{/*...*/};
void test(A &&a);
int main(int, char **){
A a;
test(copy(a)); //OK, copied
test(move(a)); //OK, moved
test(A()); //OK, temporary object
test(copy(A())); //OK, copying temporary object
//You can disable this behavior by letting copy accepts T &
//test(move(A())); You should never move a temporary object
//It is not good to have a rvalue version of move.
//test(a); forbidden, you have to say weather you want to copy or move
//from a lvalue reference.
}
C++ Solutions
Solution 1 - C++
This issue is addressed by lambda generalized capture in C++14:
// 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);});
Solution 2 - C++
You cannot permanently capture a unique_ptr
in a lambda. Indeed, if you want to permanently capture anything in a lambda, it must be copyable; merely movable is insufficient.
This could be considered a defect in C++11, but you would need some syntax to explicitly say that you wanted to move the unique_ptr
value into the lambda. The C++11 specification is very carefully worded to prevent implicit moves on named variables; that's why std::move
exists, and this is a good thing.
To do what you want will require either using std::bind
(which would be semi-convoluted, requiring a short sequence of binds
) or just returning a regular old object.
Also, never take unique_ptr
by &&
, unless you are actually writing its move constructor. Just take it by value; the only way a user can provide it by value is with a std::move
. Indeed, it's generally a good idea to never take anything by &&
, unless you're writing the move constructor/assignment operator (or implementing a forwarding function).
Solution 3 - C++
The "semi-convoluted" solution using std::bind
as mentioned by Nicol Bolas is not so bad after all:
std::function<void ()> getAction(std::unique_ptr<MyClass>&& psomething)
{
return std::bind([] (std::unique_ptr<MyClass>& p) { p->do_some_thing(); },
std::move(psomething));
}
Solution 4 - C++
A sub-optimal solution that worked for me was to convert the unique_ptr
to a shared_ptr
and then capture the shared_ptr
in the lambda.
std::function<void()> getAction(std::unique_ptr<MyClass> psomething)
{
//The caller given ownership of psomething
std::shared_ptr<MyClass> psomethingShared = std::shared_ptr<MyClass>(std::move(psomething));
return [psomethingShared]()
{
psomethingShared->do_some_thing();
};
}
Solution 5 - C++
I used this really dodgy workaround, which involves sticking the unique_ptr
inside a shared_ptr
. This is because my code required a unique_ptr
(due to an API restriction) so I couldn't actually convert it to a shared_ptr
(otherwise I'd never be able to get my unique_ptr
back).
My justification for using this abomination is that it was for my test code, and I had to std::bind
a unique_ptr
into the test function call.
// Put unique_ptr inside a shared_ptr
auto sh = std::make_shared<std::unique_ptr<Type>>(std::move(unique));
std::function<void()> fnTest = std::bind([this, sh, input, output]() {
// Move unique_ptr back out of shared_ptr
auto unique = std::move(*sh.get());
// Make sure unique_ptr is still valid
assert(unique);
// Move unique_ptr over to final function while calling it
this->run_test(std::move(unique), input, output);
});
Now calling fnTest()
will call run_test()
while passing the unique_ptr
to it. Calling fnTest()
a second time will result in an assertion failure, because the unique_ptr
has already been moved/lost during the first call.