1. ホーム
  2. c++

[解決済み] ラムダでの移動キャプチャ

2022-04-16 11:48:48

質問

C++11のラムダでmove(rvalue referenceともいう)を捕捉するにはどうすればよいですか?

このようなことを書こうとしています。

std::unique_ptr<int> myPointer(new int);

std::function<void(void)> example = [std::move(myPointer)]{
   *myPointer = 4;
};

解決方法は?

C++14の一般化ラムダキャプチャ

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

しかし、キャプチャーした変数は、そうやって何でも初期化できるという意味では、もっと一般的なものです。

auto lambda = [value = 0] mutable { return ++value; };

C++11では、これはまだ可能ではありませんが、ヘルパー型に関わるいくつかのトリックがあります。幸いなことに、Clang 3.4コンパイラはすでにこの素晴らしい機能を実装しています。このコンパイラは2013年12月か2014年1月にリリースされる予定ですが、その場合は 最近のリリースペース を維持する予定です。

UPDATE Clang 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) };
}

そして、この関数のテストケースは、私の 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() );
}

ここでの欠点は lambda はコピー可能であり、コピーした際に rref_impl が失敗し、実行時バグが発生します。以下の方法は、コンパイラがエラーを検出するため、より良い、さらに汎用的な解決策になるかもしれません。

C++11で一般化されたラムダキャプチャをエミュレートする

もう一つ、一般化されたラムダ・キャプチャーを実装する方法について考えてみましょう。関数 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() );
}

ここで lambda はファンクタオブジェクト(ほぼ本物のラムダ)であり、このオブジェクトは std::move(p) に渡されるため capture() . の第2引数は capture は,キャプチャした変数を引数にとるラムダである.このとき 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) );
}

この2番目の解決策は、捕捉された型がコピー可能でない場合、ラムダをコピーすることを無効にするので、よりクリーンである。最初の解決法では、実行時に assert() .