2012-04-04 11 views
17

this questionで作業している間に、GCC(v4.7)の実装であるstd::functionが値をとると引数を移動することに気付きました。次のコードは、この動作を示しています。`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 

私たちはここを参照してくださいcmのコピーが行われていること(byValueのパラメータは値によって取られているので、合理的なようだ)が、その後2つの動きがあります。 functioncmのコピーで動作しているため、引数を移動するという事実は重要でない実装の詳細と見ることができます。しかし、この動作は、いくつかのトラブルwhen using function together with bind原因:

#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によって返されたラッパーを、複数のプレースホルダを処理する際に予期しない動作を引き起こすにもかかわらず、by-valueパラメータを使用してstd::functionに変換できますか?

+0

問題は、 'std :: function'よりもプレースホルダが多いようです。つまり、「結び」を作成するときに、元の引数から両方の期待される出力に移動が使用されるという事実。 –

+0

興味深いことに、Visual C++ 11コンパイラは、最初の例で "default copy move"を出力し、 "already moved!" 2番目にこの追加の動きがstd :: functionの機能や完全なフォワーディングの内部動作から来るのではないかと思います。 @MatthieuM。 –

+0

。あなたは詳しく説明できますか?プレースホルダの実装にはあまり慣れていません。問題がプレースホルダから来た場合、 'std :: function'を使用する代わりに、' auto'を使用して "bind-wrapper"型を推測するときに、どのように問題が発生しないのでしょうか? –

答えて

16

std::functionは、指定された引数をラップされた関数にstd::forwardで渡すように指定されています。例えばTが参照型でない場合std::forward<T>std::moveと同等であるのでstd::function<void(MoveTracker)>ため、関数呼び出しオペレータは

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

に相当し、これはあなたの最初の例における移動の一を占めます。 2番目の方法は、std::function内のインダイレクションレイヤーを通過しなければならない可能性があります。

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

+1

ああ、 'std :: forward 'が 'std :: move 'と同等であることに気付かなかった!それは多くのことを説明します。 –

関連する問題