19

この質問に取り組んでいる間、GCC(v4.7)の実装はstd::function、引数が値で受け取られるときに引数を移動することに気付きました。次のコードは、この動作を示しています。

#include <functional>
#include <iostream>

struct CopyableMovable
{
    CopyableMovable()                        { std::cout << "default" << '\n'; }
    CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; }
    CopyableMovable(CopyableMovable &&)      { std::cout << "move" << '\n'; }
};

void foo(CopyableMovable cm)
{ }

int main()
{
    typedef std::function<void(CopyableMovable)> byValue;

    byValue fooByValue = foo;

    CopyableMovable cm;
    fooByValue(cm);
}
// outputs: default copy move move

ここでは、のコピーが実行されていることがわかります( 'sパラメータは値によって取得されるcmため、これは妥当と思われます)が、2つの動きがあります。byValueはのfunctionコピーを操作しているcmため、引数を移動するという事実は、重要でない実装の詳細と見なすことができます。ただし、この動作は、 :と一緒に使用functionbindすると問題が発生します。

#include <functional>
#include <iostream>

struct MoveTracker
{
    bool hasBeenMovedFrom;

    MoveTracker()
      : hasBeenMovedFrom(false)
    {}
    MoveTracker(MoveTracker const &)
      : hasBeenMovedFrom(false)
    {}
    MoveTracker(MoveTracker && other)
      : hasBeenMovedFrom(false)
    {
        if (other.hasBeenMovedFrom)
        {
            std::cout << "already moved!" << '\n';
        }
        else
        {
            other.hasBeenMovedFrom = true;
        }
    }
};

void foo(MoveTracker, MoveTracker) {}

int main()
{
    using namespace std::placeholders;
    std::function<void(MoveTracker)> func = std::bind(foo, _1, _1);
    MoveTracker obj;
    func(obj); // prints "already moved!"
}

この動作は標準で許可されていますか?std::function引数を移動することはできますか?もしそうなら、プレースホルダーの複数のオカレンスを処理するときに予期しない動作をトリガーしたとしても、によって返されたラッパーを値によるパラメーターでbind変換できるのは正常ですか?std::function

4

1 に答える 1

18

std::function提供された引数を。でラップされた関数に渡すように指定されますstd::forward。たとえば、std::function<void(MoveTracker)>の場合、関数呼び出し演算子は次のようになります。

void operator(CopyableMovable a)
{
    f(std::forward<CopyableMovable>(a));
}

は参照型ではない場合std::forward<T>と同等であるため、これは最初の例の動きの1つを説明しています。2つ目は、内部の間接層を通過する必要があるためである可能性があります。std::moveTstd::function

std::bindこれは、ラップされた関数としてstd::bind使用する際に発生する問題も説明します。パラメーターを転送するよう指定されています。この場合、std::forward内部の呼び出しの結果として右辺値参照が渡されstd::functionます。したがって、バインド式の関数呼び出し演算子は、各引数への右辺値参照を転送しています。残念ながら、プレースホルダーを再利用したため、どちらの場合も同じオブジェクトへの右辺値参照になります。したがって、移動可能な型の場合、最初に作成された方が値を移動し、2番目のパラメーターは空のシェルを取得します。

于 2012-04-04T10:59:26.853 に答える