2012-01-25 5 views
4

次のコードスニペットを考慮してください。 GCC 4.6.1を使用すると、x0になり、y1になります。C++ 11スレッドパズル内のファンクタ

別のスレッドを使用する場合と使用しない場合で異なる結果が得られるのはなぜですか?どちらのバージョンでも同じ結果が得られるようにコードを変更する必要があります(つまり、整数値が1ずつ増えます)。

ありがとうございました。

struct functor{ 
    void operator()(int & x){ 
     ++x; 
    }  
}; 

void tfunc(functor & f, int & x){ 
    f(x); 
} 

int main(){ 
    functor f; 
    int x = 0, y = 0; 
    std::thread t = std::thread(tfunc, f, x); 
    t.join(); 
    std::cout << "with thread " << x << std::endl;  
    f(y); 
    std::cout << "without thread " << y << std::endl; 
} 
+1

'x'をvolatileにしてみてください。 'std :: thread'が作成された後、' join'が返る前にコンパイラがその値をフェッチすることは合法です。 – spraff

+2

@spraff: 'volatile'は適切な同期の代用ではありません(これは同期の問題ではありません)。 –

+0

UPDATE。 'int *'が 'int 'の代わりに' tfunc'に渡された場合に動作します。 Tres bizarre。 – user92382

答えて

2

何が起こっているのか分かりやすいです。ちょうどintを非コピー可能な型(プライベートコピーコンストラクタを持つもの)に置き換えると、コンパイラはlibstdc++が参照を使用する代わりに引数をコピーしようとする正確な場所を特定します。私の場合は、<tuple>標準ヘッダーの138行目です。

これが標準の正しい実装であるかどうかは、現時点ではわかりません。

UPDATE標準はstd::thread::threadの各引数はMoveConstructible要件を満たす必要があること、およびスレッド関数に渡された実引数が入居構築std::thread::thread引数からであることを述べています。これは

  1. スレッド関数は引数のコピーを取得し、
  2. 原稿をうまく過程で破壊することができることを意味しています。

参考になったものはうまくいきません。

+1

その場合、 'std :: thread'の実装が、' std :: ref'に参照引数をラップするオーバーロードを追加するための修正であると思います。 – spraff

+0

@spraff:更新を参照してください。 –

+0

@spraff:修正は必要ありません - std :: threadはすでにstd :: refを処理しています。プログラムを 'std :: thread t = std :: thread(tfunc、f、std :: ref(x));'に変更すると、動作します。これはまさに 'std :: ref'が存在する状況の一種です。 –

0

std::thread(tfunc, f, x)が呼び出されたときにxがコピーされていて、一時的な値への参照がファンクタに渡されたように見えるため、ファンクタの呼び出しによってxの値は変更されません。私は一般的にSTLアルゴリズム/関数は常に引数をコピーすると思います。ファンクタコールでxを変更する場合は、ポインターを使用することも考えられます。ポインターがコピーされても、コピーは同じアドレスを指しています。