序章
最近、次のようなマルチスレッド コードを見ました。
typedef unsigned long long ULL;
void accumulator_function(const std::vector<int> &v, ULL &acm,
unsigned int beginIndex, unsigned int endIndex)
{
acm = 0;
for (unsigned int i = beginIndex; i < endIndex; ++i)
{
acm += v[i];
}
}
int main()
{
ULL acm1 = 0;
ULL acm2 = 0;
std::vector<int> v = {
1,2,3,4,5,6,7,8,9,10 };
std::thread t1(accumulator_function, std::ref(v),
std::ref(acm1), 0, v.size() / 2);
std::thread t2(accumulator_function2, std::ref(v),
std::ref(acm2), v.size() / 2, v.size());
t1.join();
t2.join();
std::cout << acm1 << "+" << acm2 << "=" << acm1 + acm2 << std::endl;
return 0;
}
スレッド作成部分は std::thread t1(accumulator_function2, std::ref(v), std::ref(acm1), 0, v.size() / 2); を使用し、対応する関数は void accumulator_function( const std::vector &v, ULL &acm, unsigned int beginIndex, unsigned int endIndex), この関数のパラメータはベクトル参照, 計算結果 acm への参照, 変数の最初と最後の位置を記録するインデックスです.ベクター。その中で、std::ref() は C++ Primer で以前に見られたもので、& に似ているはずなのに、なぜ ref を使用して参照形式に変換する必要があるのでしょうか。すぐにすべての std::ref() を削除し、もう一度実行すると、結果はエラーになります...興味深いことに、それを調べることができます。
お問い合わせの流れ
引用された例は次の 1 つで始まります。
void fun(vector<int> &a, int i)
{
a[i] = 20;
}
int main()
{
std::vector<int> v = {
1,1,1,1,1,1 };
for (auto x : v)
cout << x << ' ';
cout << endl;
fun(v, 3);
for (auto x : v)
cout << x << ' ';
}
// Output:
// 1 1 1 1 1 1
// 1 1 1 20 1 1
通常の参照であれば、fun(v, 3) を呼び出すだけでよく、なぜ例で fun(std::ref(v),...) という形式を使用しているのですか? std::ref() は & とは異なるということですか? 例を書いて見てください:
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
int main()
{
int x = 5;
std::cout << boolalpha << is_same<int&, decltype(ref(x))>::value;
return 0;
}
出力された答えは偽です! std::ref() がオブジェクトへの参照を返さない場合、何を返すのでしょうか? マニュアルを確認してください: 関数テンプレート ref と cref は std::reference_wrapper 型オブジェクトを生成するヘルパー関数であり、テンプレート引数を使用して結果を決定するテンプレート引数を推定します。そのため、std::ref() が返すのは、実際には T& ではなく reference_wrapper であり、コピーできない型のオブジェクトへの参照からコピー可能なオブジェクトを生成できます。std::reference_wrapper のインスタンスはオブジェクト (コンテナーにコピーまたは格納できます) ですが、暗黙的に T& に変換できるため、基になる型を参照によって受け取る関数の引数として使用できます。
上記の例を変更して結果を確認します。
#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
int main()
{
int x = 5;
std::cout << boolalpha << is_same<int&, decltype(ref(x).get())>::value;
return 0;
}
が真になります。reference_wrapper は & と同じではありませんが、get() 関数を使用すると & タイプになります。しかし、なぜマルチスレッドの例で std::ref() を使用するのでしょうか?
その理由は、関数型プログラミング (std::bind など) が使用される場合、パラメーターは参照ではなく直接コピーされるためです。詳細については、次の文を参照してください: std::reference_wrapper は、std::bind または std::thread のコンストラクターへの参照によってオブジェクトを渡すために使用されます。
または、コードを使用して理解します。
#include <functional>
#include <iostream>
void f(int& n1, int& n2, const int& n3)
{
std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
++n1; // increments the copy of n1 stored in the function object
++n2; // increments the main()'s n2
}
int main()
{
int n1 = 1, n2 = 2, n3 = 3;
std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));
n1 = 4;
n2 = 5;
n3 = 6;
std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
bound_f();
std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}
答えは次のとおりです。
Before function: 4 5 6
In function: 1 5 6
After function: 4 6 6
上記のコードが std::bind を実行した後、関数 f() の n1 の値は 1 のままで、n2 と n3 は変更された値に変更されます。std::bind が参照の代わりにパラメーターのコピーを使用することを説明します。C++11 の設計者は、bind はデフォルトで copy を使用する必要があると考えています. ユーザーがそれを必要とする場合は、std::ref() を追加するだけです. 同じことが std::thread にも当てはまります。
結論は
std::ref は参照渡しをシミュレートしようとするだけで、実際には参照になることはできません. 非テンプレートの場合, std::ref は参照渡しをまったく実現できません. テンプレートが自動的に型を推測する場合にのみ, ref はパッケージ化型 reference_wrapper を使用して、元の値の型が認識されるようになり、reference_wrapper は参照される値の参照型に暗黙的に変換できますが、& 型として使用することはできません。
冒頭のマルチスレッド コードに戻りますが、スレッド メソッドが参照を転送する場合、浅いコピーではなくパラメーター参照を使用したいので、参照転送には ref を使用する必要があります。