ラムダ式でムーブキャプチャ
ラムダ式での変数のキャプチャはコピーまたは参照で行いますが,ムーブするための標準的な方法はありません.
std::unique_ptr<int> p(new int(23)); auto f = [std::move(p)] { std::cout << *p << '\n'; }; // こんな書き方はない
どうしても変数 p をムーブしたい場合には,別の変数 p_ を用意して,p_ がコピーキャプチャされたときに p がムーブされるようにする方法があります.以下にそのためのクラステンプレート move_capture を示します.
#include <cassert> #include <memory> #include <new> #include <type_traits> #include <utility> template <class T> struct move_capture { private: bool has_obj; union { T *ptr; T obj; }; public: move_capture(T &t) noexcept : has_obj(false), ptr(std::addressof(t)) {} move_capture(move_capture const &x) { construct(x); } move_capture(move_capture &&x) : has_obj(x.has_obj) { if (x.has_obj) ::new(std::addressof(obj)) T(std::move(x.obj)); else ptr = x.ptr; } move_capture &operator=(move_capture const &) = delete; ~move_capture() { if (has_obj) obj.~T(); } T &get() noexcept { assert(has_obj); return obj; } T const &get() const noexcept { assert(has_obj); return obj; } private: template <class U, typename std::enable_if<std::is_copy_constructible<U>::value>::type * = nullptr> void construct(move_capture<U> const &x) { has_obj = true; if (x.has_obj) ::new(std::addressof(obj)) T(x.obj); else ::new(std::addressof(obj)) T(std::move(*x.ptr)); } template <class U, typename std::enable_if<!std::is_copy_constructible<U>::value>::type * = nullptr> void construct(move_capture<U> const &x) { assert(!x.has_obj); has_obj = true; ::new(std::addressof(obj)) T(std::move(*x.ptr)); } }; template <class T> inline move_capture<T> make_move_capture(T &t) { return move_capture<T>(t); }
使い方としては,make_move_capture で p から p_ を作り,それをコピーキャプチャします.
// ポインタの参照先を表示する関数テンプレート template <class P> void print(P const &p) { if (p) std::cout << *p << '\n'; else std::cout << "null\n"; } void example() { std::unique_ptr<int> p(new int(23)); auto p_ = make_move_capture(p); print(p); // 23 auto f = [p_] { // 元の変数の値にアクセスするときは .get() を使う print(p_.get()); }; print(p); // null f(); // 23 auto g = std::move(f); f(); // null g(); // 23 }
もっとも,どうしてもムーブキャプチャしなければならない場面はそんなにないと思います.