2012-05-08 4 views
10

std :: bindとstd :: threadのデフォルトの動作は、渡された引数をコピー(または移動)し、参照ラッパーを使用する参照セマンティクスを使用することです。std :: bindとstd :: threadの背後にある根拠は何ですか?引数は常にコピーされますか?

  1. なぜこれが良いデフォルト動作をするのか誰にも分かりますか? Esp。 r値の参照と完全な転送を伴うC++ 11では、引数を完全に転送するほうが理にかなっているようです。

  2. std :: make_sharedしかし、必ずしもコピー/移動するのではなく、提供された引数を完全に転送するだけではありません。なぜここに引数を渡すのに2つの一見違う振る舞いがありますか?

+0

ローカルに参照をバインドすると、問題が発生します。 – mfontanini

+1

呼び出された関数がローカルのスコープを超えて存在しない場合は、私はなりません。あなたの足を吹き飛ばすことができる多くのことがC++にあります。私の質問は、この行動がそれほど直感的ではないように見える点です。 – ryaner

+0

ある時点で、私の答えに「とその地方が範囲外になる」が含まれていましたが、明らかに編集後に消去されてしまいました:S – mfontanini

答えて

13

make_sharedと呼ばれるコンストラクタに転送します。です。コンストラクタが参照セマンティクスによる呼び出しを使用する場合は、参照を取得します。値で呼び出すと、コピーが作成されます。いずれにしても問題はありません。

bindは、ローカルコンテキストがなくなる可能性がある未知の点で呼び出される関数への遅延呼び出しを作成します。 bindが完全転送を使用していた場合は、実際に呼び出されたときに参照で送信され、実際には呼び出されていない引数をコピーする必要があります。どこかに格納し、そのストレージを管理します。現在のセマンティクスではbindあなたのためにそれを行います。

2

最も可能性の高い理由は、単にそのC++である(常にはstd :: make_sharedない対/移動をコピーするのstd ::スレッドとstd ::バインドは)ほとんどどこでも、デフォルトで値のセマンティクスを使用しています。参照を使用すると、参照されるオブジェクトの存続期間に関する問題が簡単に発生する可能性があります。

+0

プログラマーが最高のものを知っているというC++の原則ですか?参照が不足している場合、プログラマは、必要に応じて関数のパラメータが値であることに注意し、関数のパラメータが参照を取得するときに、何らかのリスクの参照として完全に転送されることを期待します。ちょっとした考え。 – ryaner

+1

いいえ、原則として、プログラマーは、彼が使用していない機能のためにパフォーマンスを支払わないということです。 「あなたが何をしているのか」ということは、この目的を達成するための手段にすぎず、それ自体では終わりではありません。 –

+1

C++には多くの原則があります:-)。最も基本的なものの1つは、デフォルトで値セマンティクスを使用することです。それ以外の場合に参照またはポインタを明示的に要求する必要があります。 (もちろん、標準ライブラリは常にこの規則に従うわけではありません---イテレータと述語は常に値によって異なりますが、他のものは変化する傾向があります) –

1

std :: bindは、std::bindのコールサイトから切り離された呼び出し可能ファイルを作成します。したがって、デフォルトですべての引数を値で取り込むのが非常に便利です。

一般的な使用例は、関数ポインタをどこに渡すのかを知らずに関数ポインタを渡すのと同じです。

ラムダは、引数がキャプチャされた範囲を超えてラムダが存続するかどうかをプログラマが自由に判断できるようにします。

+0

std :: threadと同じですか?スレッド開始関数の場合、ラムダとして記述される可能性は低くなります。ラムダの閉包を参照して渡すことを強制すると、奇妙に見えることがありますか? Lambdasはこれに起因するすべての問題を解決していないようです。私は思いますか? – ryaner

+0

最初の文には正当な理由があります。投票した。ありがとう。 – ryaner

7

std::bindstd::threadの両方について、指定された引数に対する関数の呼び出しは、コールサイトから遅延されます。どちらの場合も、関数が呼び出される正確なタイミングは不明です。

このような場合にパラメータを直接転送するには、参照を格納する必要があります。これは、スタックオブジェクトへの参照を格納することを意味します。呼び出しが実際に実行されたときには存在しない可能性があります。

ラムダは、キャプチャ単位で、参照または値でキャプチャするかどうかを決めることができます。 std::refを使用すると、パラメータを参照によってバインドできます。

+0

意味があります。投票しました!私は2つの答えを受け入れることが許されていれば受け入れます。ありがとう。 – ryaner

1

実際には、遅延起動ファンクタ(いくらかstd::bindのようなものですが、ネストされたバインド式/プレースホルダ機能はありません)を作成する小さなユーティリティを作成しました。私の主な動機は、私が見つけたこのケースだった直感に反する:

using pointer_type = std::unique_ptr<int>; 
pointer_type source(); 
void sink(pointer_type p); 

pointer_type p = source(); 

// Either not valid now or later when calling bound() 
// auto bound = std::bind(sink, std::move(p)); 
auto bound = std::bind(
    [](pointer_type& p) { sink(std::move(p)); } 
    , std::move(p)); 
bound(); 

sinkにその左辺値参照引数を動かす)、そのアダプタの理由はstd::bindによってコールラッパー・リターンは常に左辺値としてバインドされた引数を転送するということです。これは、例えば、 boost::bindをC++ 03に置き換えます。そのlvalueは、基本となるCallableオブジェクトの参照引数またはコピーを介した値引数にバインドされるためです。 pointer_typeは移動のみのため、ここでは機能しません。

私が得た洞察力は本当に考慮すべき二つのものがあるということです:バウンド引数はを保存しなければならない、そしてそれらがどのように(すなわち、Callableオブジェクトに渡された)を復元する必要がありますか。 std::bindが許可するコントロールは次のとおりです。引数は、浅い(std::refを使用して)または通常の方法(完全転送を伴うstd::decayを使用)に格納されます。それらは常にlvaluesとして復元されます(所有する呼び出しラッパーから継承されたcv修飾子を使用して)。あなたは私がちょうど行ったような小さなオンサイトアダプターのラムダ式で後者をバイパスすることができます。

これはおそらく、学習するのが比較的少ないため、多くの制御と多くの表現があります。比較すると、私のユーティリティはbind(f, p)(laveueとして復元とストアコピー、復元)、bind(f, ref(p))(lvalueとして復元する)、bind(f, std::move(p))(移動からの崩壊と移動、rvalueとして復元)、bind(f, emplace(p))(移動からの崩壊とストア、左辺値として復元)。これはEDSLの学習に似ています。

+0

非常に興味深い考えです。私たちが実装を指摘して、それを探求することができますか?また、結果として得られるファンクタは論理的に1回だけ呼び出すことができますか?そして結果として得られたファンクタをコピーするとどうなりましたか? – authchir

+0

@authchir [ここにコードします](https://bitbucket.org/mickk/annex/src/8166e2d92508/include/annex/make_invoke.hpp)(そして 'emplace'のようなものは[ここ](https:// bitbucket .org/mickk/annex/src/8166e2d92508/include/annex/val.hpp))。ファンクタを一度呼び出すのは正しいだけです(空の 'pointer_type'をさらに呼び出す)。また、呼び出しラッパーはmove-onlyです。これは、すべて同じ 'std :: bind'の使い方にも当てはまります。 –

+0

また、[単体テスト](https://bitbucket.org/mickk/annex/src/8166e2d92508/unit/make_invoke.cpp)は、一般的な使い方を示しています。 –

関連する問題