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


99

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


4
基本的には.、代わりに使用するポインターです->
MM

5
@MMいいえ、使用.しても、提案したとおりには機能しません(ある時点でオペレータードットの提案が採用および統合されない限り:))
コロンボ

3
このような質問は、新しいC ++を使用する必要があるときに私を不満にします。
Nils

Columboをフォローアップするには、std :: reference_wrapperをそのget()メンバー関数で使用するか、基になる型に暗黙的に変換して使用します。
Max Barraclough

回答:


88

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

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

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

これが機能するのは…

  • reference_wrappers オーバーロード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())は、ワンライナーであるため、インライン化する必要があります。

reference_wrapperstd::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を指定します。r2const int参照し、への参照のみを生成しますconst intconstファンクタを含むラッパーを参照する呼び出しは、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_wrappersには暗黙の変換演算子があり、それらがラップするオブジェクトのように呼び出すことができます。

    int i;
    int& ref = std::ref(i); // Okay
  • reference_wrapper■ポインタとは異なり、null状態はありません。参照または別のいずれかreference_wrapperで初期化する必要があります。

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


あるstd::make_tuple(std::ref(i));よりも優れてstd::make_tuple(&i);何らかの方法で?
Laurynas Lazauskas 2014年

6
@LaurynasLazauskas違います。後者iは、への参照ではなくへのポインタを保存します。
コロンボ2014年

うーん...私はまだこれらの2つを区別することができないと思います...ええと、ありがとう。
Laurynas Lazauskas 2014年

@Columbo null状態がない場合、参照ラッパーの配列はどのようにして可能ですか?通常、配列はすべての要素がnull状態に設定された状態から始まりませんか?
anatolyg 2014年

2
@anatolygその配列の初期化を妨げているものは何ですか?
コロンボ2014年

27

少なくとも、以下の2つの動機付けの目的がありstd::reference_wrapper<T>ます。

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

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

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

  2. 使用する場合std::reference_wrapper<T>std::make_tuple()対応するタプル要素になるT&のではなくT

    T object;
    f(std::make_tuple(1, std::ref(object)));

最初のケースのコード例を教えてください。
user1708860 14年

1
@ user1708860:あなたは与えられたもの以外を意味します...?
DietmarKühl2014年

(楽しさがオブジェクトではなく機能でなければ...)私はどのように使用されるかを理解していないので、私はSTDに行く実際のコード:: refの(楽しい)を意味
user1708860

2
@ user1708860:はい、おそらくfun関数オブジェクト(つまり、関数呼び出し演算子を持つクラスのオブジェクト)であり、関数ではありません。funたまたま実際の関数であるstd::ref(fun)場合、目的がなく、コードが遅くなる可能性があります。
DietmarKühl2014年

23

自己文書化コードに関するもう1つの違い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

3
それがc ++ 11より前のコードである場合を除き、最初の例は、たとえばインデックスに基づくキャッシュルックアップの場合など、オプションの所有されていない値を示す必要があります。stdがnull以外の所有値(一意で共有のバリアント)を表すための標準を提供してくれればいいのですが
Bwmat

C ++ 11ではそれほど重要ではないかもしれません。ほとんどの場合、ベアポインターはほとんど常に借用された値です。
エリング

reference_wrapper未所有であることが明らかであるだけでなく、それができないことnullptr(shenanigansなし)のためにrawポインターよりも優れているため、ユーザーはnullptr(shenanigansなしで)パスできないことを知っており、あなたがする必要がないことを知っています確認してください。
underscore_d

19

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

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

基本的にはのCopyAssignableバージョンですT&。参照が必要なときはいつでも、割り当て可能、使用、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

1
@LaurynasLazauskasラッパーに含まれる関数オブジェクトを直接呼び出すことができます。それも私の回答で説明されています。
コロンボ2014年

2
参照実装は内部の単なるポインタなので、ラッパーが間接性やパフォーマンスのペナルティを追加する理由を理解できません
Riga

4
リリースコードに関しては、単純な参照よりも間接的なものではありません
Riga

3
私はコンパイラが簡単なreference_wrapperコードをインライン化し、ポインタまたは参照を使用するコードと同じになることを期待します。
David Stone

4
@LaurynasLazauskas:std::reference_wrapperオブジェクトがnullにならないことが保証されています。クラスのメンバーについて考えてみましょうstd::vector<T *>。すべてのクラスコードを調べて、このオブジェクトがをnullptrベクターに格納できるかどうかを確認するstd::reference_wrapper<T>必要がありますが、では、有効なオブジェクトがあることが保証されています。
David Stone、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.