Lal*_*and 135 c++ lambda rvalue-reference c++11
如何通过C++ 11 lambda中的move(也称为右值引用)捕获?
我想写这样的东西:
std::unique_ptr<int> myPointer(new int);
std::function<void(void)> example = [std::move(myPointer)]{
*myPointer = 4;
};
Run Code Online (Sandbox Code Playgroud)
Ral*_*zky 140
在C++ 14中,我们将有所谓的广义lambda捕获.这可以实现移动捕获.以下是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 ); } );
Run Code Online (Sandbox Code Playgroud)
但是在某种意义上,捕获的变量可以用这样的方式初始化,这是更普遍的:
auto lambda = [value = 0] mutable { return ++value; };
Run Code Online (Sandbox Code Playgroud)
在C++ 11中,这还不可能,但有一些涉及帮助器类型的技巧.幸运的是,Clang 3.4编译器已经实现了这个非常棒的功能.如果将保留最近的发布速度,编译器将于2013年12月或2014年1月发布.
更新:该锵3.4编译器发布2014年1月6日与所述特征.
这是辅助函数的一个实现,make_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) };
}
Run Code Online (Sandbox Code Playgroud)
这是在我的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() );
}
Run Code Online (Sandbox Code Playgroud)
这里的缺点是可lambda复制,并且在复制时,复制构造函数中的断言rref_impl失败会导致运行时错误.以下可能是更好,甚至更通用的解决方案,因为编译器将捕获错误.
关于如何实现广义lambda捕获,还有一个想法.该函数的使用capture()(其实现进一步发现)如下:
#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() );
}
Run Code Online (Sandbox Code Playgroud)
这lambda是一个仿函数对象(几乎是一个真正的lambda),std::move(p)它在传递给它时被捕获capture().第二个参数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) );
}
Run Code Online (Sandbox Code Playgroud)
第二种解决方案也更清晰,因为如果捕获的类型不可复制,它会禁用复制lambda.在第一个只能在运行时检查的解决方案assert().
mar*_*n78 70
您还可以使用std::bind捕获unique_ptr:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
Run Code Online (Sandbox Code Playgroud)
Cha*_*acy 19
你可以实现大部分你想要的东西std::bind,比如:
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));
Run Code Online (Sandbox Code Playgroud)
这里的诀窍是,不是捕获捕获列表中的仅移动对象,而是将其作为参数,然后使用部分应用程序std::bind来使其消失.请注意,lambda 通过引用获取它,因为它实际上存储在绑定对象中.我还添加了写入实际可移动对象的代码,因为这是您可能想要做的事情.
在C++ 14中,您可以使用通用lambda捕获来实现相同的目的,使用以下代码:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Run Code Online (Sandbox Code Playgroud)
但是这段代码并没有给你带来任何你在C++ 11中没有的东西std::bind.(在某些情况下,广义lambda捕获更强大,但在这种情况下不是.)
现在只有一个问题; 你希望把这个功能在std::function,但类需要的功能时,拷贝构造,但它不是,它只是MoveConstructible因为它的存储std::unique_ptr是不是可复制构造.
您可以使用包装器类和另一个间接层来解决问题,但也许您根本不需要std::function.根据您的需要,您可以使用std::packaged_task; 它做同样的工作std::function,但它不要求功能可复制,只能移动(类似地,std::packaged_task只能移动).缺点是,因为它打算与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
}
Run Code Online (Sandbox Code Playgroud)
我已将上述程序放在Coliru上,因此您可以运行并使用代码.
这是一些典型的输出......
- &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
Run Code Online (Sandbox Code Playgroud)
您可以看到堆位置被重用,表明它std::unique_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
Run Code Online (Sandbox Code Playgroud)
所以我们看到函数已被移动,但不是被移动到堆上,而是std::packaged_task在堆栈中的那个内部.
希望这可以帮助!
晚了,但因为有些人(包括我)仍然停留在 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.
Run Code Online (Sandbox Code Playgroud)
.
如果发生非常罕见的情况,它确实move是指针所必需的(例如,由于删除持续时间过长,您想在单独的线程中显式删除指针,或者性能绝对至关重要),这几乎是我仍然使用的唯一情况C++11 中的原始指针。这些当然也是可复制的。
通常我用 a 标记这些罕见的情况,//FIXME:以确保一旦升级到 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;
Run Code Online (Sandbox Code Playgroud)
是的,这些天(并非没有理由)原始指针非常不受欢迎,但我真的认为在这些罕见(和临时!)情况下,它们是最好的解决方案。
| 归档时间: |
|
| 查看次数: |
59746 次 |
| 最近记录: |