C++ 在lambda中移动捕获
如何在C++11 lambda中通过移动(也称为右值引用)进行捕获 我试着写这样的东西:C++ 在lambda中移动捕获,c++,lambda,c++11,rvalue-reference,C++,Lambda,C++11,Rvalue Reference,如何在C++11 lambda中通过移动(也称为右值引用)进行捕获 我试着写这样的东西: std::unique_ptr<int> myPointer(new int); std::function<void(void)> example = [std::move(myPointer)]{ *myPointer = 4; }; std::unique_ptr<int> myPointer(new int{42}); auto lambda = st
std::unique_ptr<int> myPointer(new int);
std::function<void(void)> example = [std::move(myPointer)]{
*myPointer = 4;
};
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));
std::unique_ptr myPointer(新int);
std::函数示例=[std::move(myPointer)]{
*myPointer=4;
};
您还可以使用std::bind
捕获唯一的\u ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
std::function f=std::bind(
[](std::unique_ptr&p){*p=4;},
标准::移动(myPointer)
);
C++14中的广义lambda捕获
在C++14中,我们将使用所谓的。这将启用移动捕获。以下是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 ); } );
在C++11中,这还不可能实现,但有一些技巧涉及助手类型。幸运的是,Clang3.4编译器已经实现了这个令人敬畏的特性。该编译器将于2013年12月或2014年1月发布,前提是保留该版本
更新:于2014年1月6日发布了上述功能
移动捕获的解决方法
下面是一个帮助函数make\u rref
的实现,它有助于人工移动捕捉
#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) };
}
这里的lambda
是一个函子对象(几乎是真实的lambda),它在传递给capture()
时捕获了std::move(p)
。capture
的第二个参数是lambda,它将捕获的变量作为参数。当lambda
用作函数对象时,传递给它的所有参数将作为捕获变量后的参数转发给内部lambda。(在我们的案例中,没有进一步的论据需要转发)。本质上,与前面的解决方案相同。以下是如何实现capture
:
#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) );
}
#包括
模板
类捕获\u impl
{
tx;
F;
公众:
捕获执行(T&x、F&F)
:x{std::forward(x)},f{std::forward(f)}
{}
模板自动运算符()(Ts&…args)
->decltype(f(x,std::forward(args)…)
{
返回f(x,std::forward(args)…);
}
模板自动运算符()(Ts&…args)常量
->decltype(f(x,std::forward(args)…)
{
返回f(x,std::forward(args)…);
}
};
模板
捕获\u impl捕获(T&&x、F&&F)
{
返回捕获\u impl(
标准:正向(x),标准:正向(f);
}
第二个解决方案也更干净,因为如果捕获的类型不可复制,它将禁用复制lambda。在第一个解决方案中,只能在运行时使用
assert()
进行检查,您可以使用它来实现大部分需要的功能,如下所示:
std::unique_ptr<int> myPointer(new int);
std::function<void(void)> example = [std::move(myPointer)]{
*myPointer = 4;
};
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));
但是,这段代码并不能为您购买任何在C++11中没有的东西。(在某些情况下,广义lambda捕获功能更强大,但在本例中并非如此。)
现在只有一个问题;您想将此函数放入,但该类要求该函数为,但它不是,这只是因为它存储了一个std::unique\u ptr
,而不是
您需要使用包装器类和另一个间接级别来解决这个问题,但可能根本不需要。根据您的需要,您可以使用;它的工作与相同,但它不要求该函数是可复制的,只能移动(类似地,只能移动)。缺点是,因为它打算与std::future一起使用,所以只能调用一次
下面是一个简短的程序,展示了所有这些概念
#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
}
您可以看到堆位置被重用,这表明std::unique\u ptr
工作正常。当我们将函数隐藏在一个包装器中时,您还可以看到函数本身在移动,我们将其馈送到std::function
如果我们切换到使用std::packaged_task
,它将成为最后一部分
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
因此,我们看到函数已被移动,但它并没有移动到堆中,而是在堆栈上的std::packaged_任务
中
希望这有帮助 我在看这些答案,但我发现bind很难阅读和理解。所以我所做的是创建一个类,该类在copy上移动。通过这种方式,它对它正在做的事情是明确的
#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";
}
那么,指针地址可能会有所不同。;)
很晚了,但由于有些人(包括我)仍然坚持使用c++11:
老实说,我真的不喜欢任何张贴的解决方案。我相信它们会起作用,但它们需要很多额外的东西和/或神秘的std::bind
语法。。。我认为,在升级到C++ >=14时,无论如何都不需要为这样一个临时解决方案付出努力。因此,我认为最好的解决方案是完全避免c++11的移动捕获
通常最简单、可读性最好的解决方案是使用std::shared_ptr
,它是可复制的,因此完全可以避免移动。缺点是,它的效率稍低,但在许多情况下,效率并不是那么重要
// 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.
是的,这些天来,原始指针是非常不受欢迎的(而且不是没有理由的),但我真的认为在这些罕见的(和暂时的!)情况下,它们是最好的解决方案。这似乎对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;
}
#包括
#包括
结构Foo{};
空栏(标准::唯一的\u ptr p){
std::我可以在G++-4.8-std=c++11上使用这么长时间吗?我还以为这是c++11的功能。现在我习惯了使用它,突然意识到它是c++14的功能…我该怎么办!!@RnMss你指的是什么功能?广义的lambda捕获?@RalphTandetzky我想是的,我刚刚检查了一下,与XCode捆绑的clang版本似乎是正确的也支持它!它给出了一个警告,说明它是C++1y扩展,但确实有效。@RNMS要么使用moveCapture
包装器将它们作为参数传递(此方法在上面和Capn'Proto中使用,这是Protobuff的创建者创建的库),要么接受您需要支持它的编译器:PNo,它实际上是
#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";
}
value: 1
object still not deleted
value: 1
Destroying 000000DFDD172280
object was deleted
// 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.
// 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;
#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;
}