Modern C++:如何让lambda表达式实现移动捕获?
lambda表达式自从C++11引入后,在C++14/17中得到完善,目前已经成为C++中极具颠覆性的语言特性。这不免让人感觉到不可思议,因为lambda表达式子并没有给语言注入新的表达力。任何lambda表达式能做实现的功能,都可以使用函数对象/函数实现,无非需要多敲几个字。但是lambda作为一种创建函数对象的手段,由于太过方便,所以才会对C++日常软件的开发产生极大的影响。
C++11的lambda表达式在语法上只能捕获值和引用,不支持移动捕获,与move语义衔接并不好。但是对于容器和其他复制成本较高的对象来说,支持移动捕获将非常有用,这将提高程序的性能。此外,像iostreams(特别是stringstream)和unique_ptrs这样仅移动的类型如果不进行包装就无法捕获它们。
考虑如下的场景:run()调用的函数对象不能通过引用捕获变量result,因为它可能在一个单独的线程中运行,结果不再可用,或者由于其他原因,这里确实需要一个移动构造的对象。下面的代码将不能通过编译,因为unique_ptr无法被拷贝。
//C++11
template <class T>
void run(T&& runnable) {
runnable();
auto result = std::make_unique<int>(42);
// error! unique_ptr isn't copyable.
run([result]() { std::cout << *result << std::endl; });
这个问题有一个令人厌恶的解决方案:编写一个移动包装类(Move Wrapper),拷贝构造函数实际上做的是移动操作:
// adapted from folly
template<class T>
class MoveWrapper {
public:
/** If value can be default-constructed, why not?
Then we don't have to move it in */
MoveWrapper() = default;
explicit MoveWrapper(T&& value) : value_(std::move(value)) {}
MoveWrapper& operator=(MoveWrapper const&) = delete;
MoveWrapper& operator=(MoveWrapper&&) = delete;
// copy is move
MoveWrapper(const MoveWrapper& other) : value_(std::move(other.value_)) {}
// move is also move
MoveWapper(MoveWrapper&& other) : value_(std::move(other.value_)) {}
// move the value out (sugar for std::move(*moveWrapper))
T&& move() { return std::move(value); }
const T& operator*() const { return value_; }
T& operator*() { return value_; }
const T* operator->() const { return &value_; }
T* operator->() { return &value_; }
private:
mutable T value_;
template <class T, class T0 = std::remove_reference_t<T>>
MoveWrapper<T0> makeMoveWrapper(T&& t) {
return MoveWrapper<T0>(std::forward<T0>(t));
这样就能实现lambda表达式的移动捕获:
auto result = std::make_unique<int>(42);
auto move_result = makeMoveWrapper(std::move(result));