Was ist der Unterschied zwischen std::reference_wrapper und einem einfachen Zeiger?

Lesezeit: 8 Minuten

Benutzer-Avatar
Laurynas Lazauskas

Warum muss man haben std::reference_wrapper? Wo soll es eingesetzt werden? Wie unterscheidet es sich von einem einfachen Zeiger? Wie ist seine Leistung im Vergleich zu einem einfachen Zeiger?

  • Es ist im Grunde ein Zeiger, den Sie verwenden . mit statt ->

    – MM

    5. November 2014 um 21:07 Uhr


  • @MM Nein, mit . funktioniert nicht so wie du vorschlägst (es sei denn, irgendwann wird der Operator-Punkt-Vorschlag übernommen und integriert 🙂 )

    – Kolumbus

    13. April 2016 um 16:45 Uhr


  • Es sind Fragen wie diese, die mich unglücklich machen, wenn ich mit dem neuen C++ arbeiten muss.

    – Nils

    25. Juli 2019 um 10:28 Uhr

  • Um Columbo weiterzuverfolgen, wird std::reference_wrapper mit seiner verwendet get() Member-Funktion oder mit ihrer impliziten Konvertierung zurück in den zugrunde liegenden Typ.

    – Max Barraclough

    30. Mai 2020 um 13:09 Uhr


Benutzer-Avatar
Columbo

std::reference_wrapper ist in Kombination mit Vorlagen nützlich. Es umschließt ein Objekt, indem es einen Zeiger darauf speichert, was eine Neuzuweisung und ein Kopieren ermöglicht, während es seine übliche Semantik nachahmt. Es weist auch bestimmte Bibliotheksvorlagen an, Verweise anstelle von Objekten zu speichern.

Betrachten Sie die Algorithmen in der STL, die Funktoren kopieren: Sie können diese Kopie vermeiden, indem Sie einfach einen Referenz-Wrapper übergeben, der auf den Funktor verweist, anstatt auf den Funktor selbst:

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

Das funktioniert, weil …

  • reference_wrappers Überlast operator() Sie können also genau wie die Funktionsobjekte aufgerufen werden, auf die sie sich beziehen:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • …(un)wie gewöhnliche Referenzen, kopieren (und zuweisen) reference_wrappers weist nur den Pointee zu.

    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>
    

Das Kopieren eines Referenz-Wrappers entspricht praktisch dem Kopieren eines Zeigers, was so billig ist, wie es nur geht. Alle Funktionsaufrufe, die der Verwendung innewohnen (z. B. die zu operator()) sollten nur eingebettet sein, da es sich um Einzeiler handelt.

reference_wrappers werden erstellt über std::ref und 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>

Das Template-Argument gibt den Typ und die CV-Qualifikation des Objekts an, auf das verwiesen wird; r2 bezieht sich auf a const int und liefert nur einen Verweis auf const int. Aufrufe, um Wrapper mit zu referenzieren const Funktoren in ihnen rufen nur auf const Mitgliedsfunktion operator()s.

Rvalue-Initialisierer sind nicht erlaubt, da ihre Zulassung mehr schaden als nützen würde. Da rvalues ​​sowieso verschoben würden (und mit garantierte Kopienentfernung selbst das wird teilweise vermieden), wir verbessern die Semantik nicht; Wir können jedoch baumelnde Zeiger einführen, da ein Referenz-Wrapper die Lebensdauer des Pointees nicht verlängert.

Bibliotheksinteraktion

Wie bereits erwähnt, kann man beauftragen make_tuple um eine Referenz im Ergebnis zu speichern tuple indem das entsprechende Argument durch a geleitet wird 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&>

Beachten Sie, dass sich dies geringfügig von unterscheidet forward_as_tuple: Hier sind Rvalues ​​als Argumente nicht erlaubt.

std::bind zeigt das gleiche Verhalten: Es wird das Argument nicht kopieren, sondern eine Referenz speichern, wenn es sich um a handelt reference_wrapper. Nützlich, wenn dieses Argument (oder der Funktor!) nicht kopiert werden muss, aber im Gültigkeitsbereich bleibt, während die bind-Funktor verwendet wird.

Unterschied zu gewöhnlichen Zeigern

  • Es gibt keine zusätzliche Ebene der syntaktischen Indirektion. Zeiger müssen dereferenziert werden, um einen lvalue zu dem Objekt zu erhalten, auf das sie sich beziehen; reference_wrappers haben eine implizite Konvertierungsoperator und können wie das Objekt aufgerufen werden, das sie umhüllen.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers haben im Gegensatz zu Zeigern keinen Nullzustand. Sie müssen mit initialisiert werden entweder eine Referenz oder eine andere reference_wrapper.

    std::reference_wrapper<int> r; // Invalid
    
  • Eine Ähnlichkeit besteht in der seichten Kopiersemantik: Zeiger und reference_wrappers können neu vergeben werden.

  • @LaurynasLazauskas Es ist anders. Letzteres, das Sie gezeigt haben, speichert einen Zeiger auf ikein Hinweis darauf.

    – Kolumbus

    5. November 2014 um 21:38 Uhr

  • @anatolyg Was hindert Sie daran, dieses Array zu initialisieren?

    – Kolumbus

    6. November 2014 um 16:12 Uhr

  • Zeiger können auch so deklariert werden, dass sie keinen Nullzustand haben nicht null

    – Rufus

    16. Februar 2017 um 4:17 Uhr

  • reference_wrappers, unlike pointers, don't have a null state. They have to be initialized with either a reference or another reference_wrapper. Seit C++17 können Sie a std::optional um eine Referenz ohne Initialisierung zu haben: std::optional<std::reference_wrapper<int>> x; auto y = 4; x = y; Der Zugriff darauf ist jedoch etwas ausführlich: std::cout << x.value().get();

    – dteod

    31. August 2019 um 23:52 Uhr


  • @Kolumbo: “[…] Ein Programm ist falsch formatiert, wenn es eine Option mit einem Referenztyp instanziiert. en.cppreference.com/w/cpp/utility/optional

    – Johann Gerell

    3. August 2021 um 8:30 Uhr

Es gibt mindestens zwei motivierende Zwecke std::reference_wrapper<T>:

  1. Es dient dazu, Objekten, die als Wertparameter an Funktionsvorlagen übergeben werden, Referenzsemantik zu verleihen. Beispielsweise haben Sie möglicherweise ein großes Funktionsobjekt, an das Sie übergeben möchten std::for_each() der seinen Funktionsobjektparameter nach Wert nimmt. Um das Kopieren des Objekts zu vermeiden, können Sie verwenden

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

    Übergabe von Argumenten als std::reference_wrapper<T> zu einem std::bind() Ausdruck ist ziemlich üblich, um Argumente nach Referenz und nicht nach Wert zu binden.

  2. Bei Verwendung eines std::reference_wrapper<T> mit std::make_tuple() das entsprechende Tupelelement wird zu a T& eher als ein T:

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

  • Können Sie bitte ein Codebeispiel für den ersten Fall geben?

    – Benutzer1708860

    5. November 2014 um 21:09 Uhr

  • @ user1708860: Du meinst andere als die angegebene …?

    – Dietmar Kühl

    5. November 2014 um 21:11 Uhr

  • Ich meine tatsächlichen Code, der zu std::ref(fun) gehört, weil ich nicht verstehe, wie er verwendet wird (es sei denn, Spaß ist ein Objekt und keine Funktion …)

    – Benutzer1708860

    5. November 2014 um 21:12 Uhr

  • @ user1708860: ja, höchstwahrscheinlich fun ist ein Funktionsobjekt (dh ein Objekt einer Klasse mit einem Funktionsaufrufoperator) und keine Funktion: if fun zufällig eine tatsächliche Funktion, std::ref(fun) haben keinen Zweck und machen den Code potenziell langsamer.

    – Dietmar Kühl

    5. November 2014 um 21:14 Uhr


Ein weiterer Unterschied in Bezug auf selbstdokumentierenden Code besteht darin, dass die Verwendung von a reference_wrapper leugnet im Wesentlichen das Eigentum an der Sache. Im Gegensatz dazu ist A unique_ptr behauptet den Besitz, während ein bloßer Zeiger im Besitz sein kann oder nicht (es ist nicht möglich, dies zu wissen, ohne sich viel verwandten Code anzusehen):

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

  • Sofern es sich nicht um Pre-C++11-Code handelt, sollte das erste Beispiel optionale, nicht besessene Werte implizieren, beispielsweise für eine Cache-Suche basierend auf dem Index. Es wäre schön, wenn std uns etwas Standardmäßiges zur Verfügung stellen würde, um einen Nicht-Null-Eigenwert darzustellen (eindeutige und gemeinsam genutzte Varianten).

    – Bwmat

    24. Dezember 2016 um 7:19 Uhr

  • Es ist vielleicht nicht so wichtig in C++11, wo nackte Zeiger ohnehin fast immer geliehene Werte sind.

    – bjaastad_e

    23. April 2020 um 15:44 Uhr


  • reference_wrapper ist rohen Zeigern nicht nur deshalb überlegen, weil es klar ist, dass es nicht besitzend ist, sondern auch, weil es das nicht sein kann nullptr (ohne Spielereien) und somit wissen die Benutzer, dass sie nicht bestehen können nullptr (ohne Spielereien) und Sie wissen, dass Sie nicht danach suchen müssen.

    – Unterstrich_d

    23. Juli 2020 um 14:37 Uhr

  • @underscore_d Was meinst du mit “ohne Spielereien”? ist es möglich mit Spielereien? Ich bin sehr neugierig

    – Spyros Mourelatos

    27. Juli 2021 um 10:20 Uhr


  • @SpyrosMourelatos Es ist möglich, wenn auch mit undefiniertem Verhalten und daher völlig nicht ratsam, … eine “Nullreferenz” zu bilden. Man kann dann die resultierende ungültige Referenz in a schieben reference_wrapper. Aber das Erstellen/Verwenden solcher Referenzen hat ein undefiniertes Verhalten, daher meine – übermäßig großzügig! – Etikettierung als “Spielerei”. Siehe Ist eine Nullreferenz möglich?

    – Unterstrich_d

    28. Juli 2021 um 11:08 Uhr

Benutzer-Avatar
Barry

Sie können es sich als bequemen Wrapper um Referenzen vorstellen, damit Sie sie in Containern verwenden können.

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

Es ist im Grunde ein CopyAssignable Version von T&. Immer wenn Sie eine Referenz wünschen, diese aber zuweisbar sein muss, verwenden Sie std::reference_wrapper<T> oder seine Hilfsfunktion std::ref(). Oder verwenden Sie einen Zeiger.


Andere Macken: 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

Und Vergleich:

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

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

1012950cookie-checkWas ist der Unterschied zwischen std::reference_wrapper und einem einfachen Zeiger?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy