12

std::bindおよびstd::threadのデフォルトの動作は、渡された引数をコピー(または移動)することであり、参照セマンティクスを使用するには、参照ラッパーを使用する必要があることはよく知られています。

  1. なぜこれが良いデフォルトの振る舞いになるのか誰かが知っていますか?Esp。右辺値参照と完全転送を使用するC++11では、引数を完全に転送する方が理にかなっているように思われます。

  2. std :: make_sharedは常にコピー/移動するわけではありませんが、提供された引数を完全に転送します。ここで引数を転送する2つの一見異なる動作があるのはなぜですか?(常にコピー/移動するstd::threadおよびstd::bindと、そうでないstd :: make_shared)

4

5 に答える 5

15

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

bindローカルコンテキストが失われる可能性があるときに、将来のいくつかの未知のポイントで呼び出される関数への遅延呼び出しを作成します。完全な転送を使用している場合bindは、通常は参照によって送信され、実際の呼び出し時に有効であることがわかっていない引数をコピーし、どこかに保存して、そのストレージを管理する必要があります。現在のセマンティクスbindでは、それが自動的に行われます。

于 2012-05-08T17:44:16.567 に答える
7

std::bindとの両方についてstd::thread、指定された引数に対する関数の呼び出しは、呼び出しサイトから延期されます。どちらの場合も、関数がいつ呼び出されるかは正確にはわかりません。

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

おっと。

Lambdasは、キャプチャごとに、参照または値でキャプチャするかどうかを決定する機能が与えられているため、これを実行できます。を使用std::refすると、参照によってパラメータをバインドできます。

于 2012-05-08T18:07:10.637 に答える
2

最も可能性の高い理由は、C++がデフォルトでほとんどすべての場所で値セマンティクスを使用していることです。また、参照を使用すると、参照されるオブジェクトの存続期間に関する問題が簡単に発生する可能性があります。

于 2012-05-08T17:20:28.850 に答える
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();

そのアダプタ(左辺値のref引数をに移動するsink)の理由は、呼び出しラッパーがstd::bind常にバインドされた引数を左辺値として転送するためです。これは、たとえばC ++ 03では問題ではありませんでした。これはboost::bind、その左辺値が、基になるCallableオブジェクトの参照引数にバインドされるか、コピーを介して値引数にバインドされるためです。pointer_typeは移動専用なので、ここでは機能しません。

私が得た洞察は、バインドされた引数を格納する方法と、それらを復元する方法(つまり、Callableオブジェクトに渡す方法)の2つを考慮する必要があるということです。付与するコントロールstd::bindは次のとおりです。引数は、浅いstd::ref方法(を使用して)または通常の方法(std::decay完全な順方向で使用)のいずれかで格納されます。それらは常に左辺値として復元されます(所有する呼び出しラッパーから継承されたcv修飾子を使用)。私が行ったように、小さなオンサイトアダプターラムダ式で後者をバイパスできることを除いて。

おそらく、学ぶことは比較的少ないので、多くの制御と多くの表現です。比較すると、私のユーティリティには、bind(f, p)(減衰してコピーを保存、左辺値として復元)、bind(f, ref(p))(浅く保存、左辺値として復元)、bind(f, std::move(p))(減衰して移動から保存、右辺値として復元)、bind(f, emplace(p))(減衰して移動から保存、左辺値として復元)などのセマンティクスがあります。これは、EDSLを学ぶようなものです。

于 2012-05-10T01:17:53.563 に答える
1

std :: bindは、の呼び出しサイトから切り離された呼び出し可能オブジェクトを作成std::bindします。したがって、デフォルトですべての引数を値でキャプチャすることは非常に理にかなっています。

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

ラムダを使用すると、プログラマーは、ラムダがキャプチャ元のスコープを超えて存続するかどうかをより柔軟に判断できます。

于 2012-05-08T17:21:17.173 に答える