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));