web-dev-qa-db-ja.com

std :: reference_wrapperと単純なポインターの違いは?

なぜ std::reference_wrapper ?どこで使用すべきですか?単純なポインターとはどう違うのですか?そのパフォーマンスは単純なポインターと比較してどうですか?

80

_std::reference_wrapper_は、テンプレートと組み合わせて使用​​すると便利です。オブジェクトへのポインタを格納することでオブジェクトをラップし、通常のセマンティクスを模倣しながら再割り当てとコピーを可能にします。また、オブジェクトの代わりに参照を保存するように特定のライブラリテンプレートに指示します。

ファンクターをコピーするSTLのアルゴリズムを検討してください。ファンクター自体ではなくファンクターを参照する参照ラッパーを渡すだけで、そのコピーを回避できます。

_unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state
_

これは…

  • …_reference_wrapper_ s overload operator() 参照する関数オブジェクトのように呼び出すことができます:

    _std::ref(myEngine)() // Valid expression, modifies myEngines state
    _
  • …(通常)参照とは異なり、_reference_wrappers_をコピー(および割り当て)すると、指示先が割り当てられます。

    _int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
    _

参照ラッパーをコピーすることは、ポインターをコピーすることと実質的に同等です。それを使用することに固有のすべての関数呼び出し(たとえば、operator()への呼び出し)は、1行であるため、単にインライン化する必要があります。

_reference_wrapper_ sは _std::ref_および_std::cref_ で作成されます:

_int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>
_

テンプレート引数は、参照されるオブジェクトのタイプとcv-qualificationを指定します。 _r2_は_const int_を参照し、_const int_への参照のみを生成します。 constファンクターを含むラッパーを参照する呼び出しは、constメンバー関数operator() sのみを呼び出します。

右辺値の初期化子は、許可するよりも害があるため、許可されません。とにかく右辺値が移動するので(および 保証されたコピーの省略 でも一部は回避されます)、セマンティクスを改善しません。ただし、参照ラッパーはポインティの有効期間を延長しないため、ダングリングポインターを導入できます。

ライブラリの相互作用

前述のように、対応する引数を_make_Tuple_に渡すことにより、結果のTupleに参照を格納するように_reference_wrapper_に指示できます。

_int i;
auto t1 = std::make_Tuple(i); // Copies i. Type of t1 is Tuple<int>
auto t2 = std::make_Tuple(std::ref(i)); // Saves a reference to i.
                                        // Type of t2 is Tuple<int&>
_

これは_forward_as_Tuple_とわずかに異なることに注意してください。ここでは、引数としての右辺値は許可されていません。

_std::bind_ は同じ動作を示します:引数をコピーしませんが、_reference_wrapper_の場合は参照を保存します。その引数(またはファンクター!)をコピーする必要はないが、bind- functorが使用されている間スコープ内に留まる場合に便利です。

通常のポインターとの違い

  • 追加のレベルの構文間接指定はありません。ポインターは、参照するオブジェクトへの左辺値を取得するために逆参照する必要があります。 _reference_wrapper_ sには暗黙的な 変換演算子 があり、ラップするオブジェクトのように呼び出すことができます。

    _int i;
    int& ref = std::ref(i); // Okay
    _
  • _reference_wrapper_ sは、ポインターとは異なり、null状態を持ちません。これらは 参照または別の_reference_wrapper_ で初期化する必要があります。

    _std::reference_wrapper<int> r; // Invalid
    _
  • 類似点は、浅いコピーのセマンティクスです。ポインターと_reference_wrapper_ sを再割り当てできます。

80
Columbo

_std::reference_wrapper<T>_には、少なくとも2つの動機付けの目的があります。

  1. 関数テンプレートに値パラメーターとして渡されるオブジェクトに参照セマンティクスを与えることです。たとえば、std::for_each()に渡す大きな関数オブジェクトがあり、その関数オブジェクトの値を値で受け取ります。オブジェクトのコピーを避けるために、次を使用できます

    _std::for_each(begin, end, std::ref(fun));
    _

    引数を_std::reference_wrapper<T>_としてstd::bind()式に渡すことは、値ではなく参照によって引数をバインドするために非常に一般的です。

  2. std::make_Tuple()で_std::reference_wrapper<T>_を使用すると、対応するTuple要素はTではなく_T&_になります。

    _T object;
    f(std::make_Tuple(1, std::ref(object)));
    _
23
Dietmar Kühl

自己文書化コードに関する別の違いは、reference_wrapperは、基本的にオブジェクトの所有権を否認します。対照的に、unique_ptrは所有権を表明しますが、裸のポインタは所有される場合とされない場合があります(関連する多くのコードを見ずに知ることはできません)。

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned
18
Edward Loper

コンテナで使用できるように、参照の便利なラッパーと考えることができます。

_std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile
_

基本的には_T&_のCopyAssignableバージョンです。参照が必要なときはいつでも、割り当て可能でなければならない場合は、_std::reference_wrapper<T>_またはそのヘルパー関数std::ref()を使用してください。または、ポインターを使用します。


その他の癖:sizeof

_sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24
_

そして比較:

_int i = 42;
assert(std::ref(i) == std::ref(i)); // ok

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
_
16
Barry