C ++ Unterschied zwischen std :: ref (T) und T &?

92

Ich habe einige Fragen zu diesem Programm:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

Die Ausgabe ist:

false

Ich möchte wissen, wenn std::refes die Referenz eines Objekts nicht zurückgibt, was macht es dann? Was ist der Unterschied zwischen:

T x;
auto r = ref(x);

und

T x;
T &y = x;

Ich möchte auch wissen, warum dieser Unterschied besteht. Warum brauchen wir std::refoder std::reference_wrapperwenn wir Referenzen haben (dh T&)?

CppNITR
quelle
2
Mögliches Duplikat von Wie ist tr1 :: reference_wrapper nützlich?
anderas
Hinweis: Was passiert, wenn Sie dies x = y; in beiden Fällen tun ?
Juanchopanza
2
Zusätzlich zu meinem doppelten Flag (letzter Kommentar): Siehe zum Beispiel stackoverflow.com/questions/31270810/… und stackoverflow.com/questions/26766939/…
anderas
2
@anderas es geht nicht um Nützlichkeit, es geht hauptsächlich um den Unterschied
CppNITR
@CppNITR Dann sehen Sie sich die Fragen an, die ich einige Sekunden vor Ihrem Kommentar verlinkt habe. Besonders der zweite ist nützlich.
anderas

Antworten:

90

Well erstellt refein Objekt des entsprechenden reference_wrapperTyps, um einen Verweis auf ein Objekt zu enthalten. Was bedeutet, wenn Sie sich bewerben:

auto r = ref(x);

Dies gibt einen reference_wrapperund keinen direkten Verweis auf x(dh T&) zurück. Dies reference_wrapper(dh r) gilt stattdessen T&.

A reference_wrapperist sehr nützlich, wenn Sie referenceein Objekt emulieren möchten, das kopiert werden kann (es ist sowohl kopierkonstruierbar als auch kopierzuweisbar ).

In C ++, wenn Sie einen Verweis (sagen wir erstellen y) zu einem Objekt (sagen wir x), dann yund xteilen die gleiche Basisadresse . Darüber hinaus ykann nicht auf ein anderes Objekt verweisen. Sie können auch kein Array von Referenzen erstellen, dh Code wie dieser löst einen Fehler aus:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

Dies ist jedoch legal:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

Wenn Sie über Ihr Problem sprechen cout << is_same<T&,decltype(r)>::value;, lautet die Lösung:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

Lassen Sie mich Ihnen ein Programm zeigen:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

Hier lernen wir drei Dinge kennen:

  1. Ein reference_wrapperObjekt (hier r) kann verwendet werden, um ein Array von Referenzen zu erstellen, mit denen dies nicht möglich warT& .

  2. rverhält sich tatsächlich wie eine echte Referenz (sehen Sie, wie r.get()=70sich der Wert von geändert haty ).

  3. rist nicht dasselbe wie T&aber r.get()ist. Dies bedeutet, dass rgilt, T&dh wie der Name schon sagt, ein Wrapper um eine Referenz ist T& .

Ich hoffe, diese Antwort ist mehr als genug, um Ihre Zweifel zu erklären.

Ankit Acharya
quelle
3
1: Nein, a reference_wrapperkann neu zugewiesen werden , aber es kann nicht "auf mehr als ein Objekt verweisen". 2/3: Fairer Punkt darüber, wo dies .get()angemessen ist - aber ohne Suffix r kann genauso verwendet werden wie T&in Fällen, in denen rdie Konvertierung operatoreindeutig aufgerufen werden kann -, sodass .get()in vielen Fällen kein Aufruf erforderlich ist, einschließlich mehrerer in Ihrem Code (der schwer zu lesen ist) wegen Platzmangels).
underscore_d
@underscore_d reference_wrapperkann eine Reihe von Referenzen enthalten, wenn Sie sich nicht sicher sind, können Sie es selbst versuchen. Plus .get()wird verwendet, wenn Sie den Wert des Objekts ändern möchten, das sich reference_wrapperbefindet, dh r=70illegal ist, sodass Sie es verwenden müssen r.get()=70. Versuchen Sie es selbst !!!!!!
Ankit Acharya
1
Nicht wirklich. Verwenden wir zunächst einen genauen Wortlaut. Sie zeigen einen reference_wrapper, der eine Referenz auf ein Array enthält - keinen reference_wrapper, der selbst "Referenz auf mehr als ein Objekt" enthält . Der Wrapper enthält nur eine Referenz. Zweitens kann ich einen nativen Verweis auf ein Array erhalten - sind Sie sicher, dass Sie die (Klammern) nicht einfach vergessen haben int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper ist hier nichts Besonderes, da native T& funktioniert .
underscore_d
1
@AnkitAcharya Ja :-) aber um genau zu sein, effektives Ergebnis beiseite, bezieht sich nur auf ein Objekt. Wie auch immer, Sie haben natürlich Recht, dass im Gegensatz zu einem normalen Schiedsrichter die wrapperin einen Container gehen können. Das ist praktisch, aber ich denke, die Leute interpretieren dies falsch als fortgeschrittener als es wirklich ist. Wenn ich eine Reihe von 'Refs' will, überspringe ich normalerweise den Mittelsmann mit vector<Item *>, worauf es wrapperankommt ... und hoffe, dass die Anti-Zeiger-Puristen mich nicht finden. Die überzeugenden Anwendungsfälle dafür sind unterschiedlich und komplexer.
underscore_d
1
"Ein reference_wrapper ist sehr nützlich, wenn Sie eine Referenz eines Objekts emulieren möchten, das kopiert werden kann." Aber besiegt das nicht den Zweck einer Referenz? Sie beziehen sich auf die eigentliche Sache. Das ist eine Referenz. Eine Referenz, die kopiert werden kann, erscheint sinnlos, denn wenn Sie sie kopieren möchten, möchten Sie zunächst keine Referenz, sondern nur das Originalobjekt kopieren. Es scheint eine unnötige Komplexitätsebene zu sein, um ein Problem zu lösen, das überhaupt nicht existieren muss.
stu
53

std::reference_wrapper wird von Standardeinrichtungen erkannt, Objekte in Referenzkontexten als Referenz übergeben zu können.

Sie können beispielsweise std::binddas std::ref()zu etwas aufnehmen, es nach Wert übertragen und später wieder in eine Referenz entpacken.

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

Dieses Snippet gibt Folgendes aus:

10
20

Der Wert von iwurde f1an dem Punkt gespeichert (nach Wert genommen), an dem er initialisiert wurde, f2hat jedoch einen By- std::reference_wrapperWert beibehalten und verhält sich daher so, als hätte er einen aufgenommen int&.

QUentin
quelle
2
@CppNITR sicher! Geben Sie mir einen Moment Zeit, um eine kleine Demo zusammenzustellen :)
Quentin
1
Was ist mit dem Unterschied zwischen T & & Ref (T)
CppNITR
3
@CppNITR std::ref(T)gibt a zurück std::reference_wrapper. Es ist kaum mehr als ein umhüllter Zeiger, wird aber von der Bibliothek als "Hey, ich soll eine Referenz sein! Bitte verwandeln Sie mich wieder in einen, wenn Sie fertig sind, mich herumzugeben" erkannt.
Quentin
37

Eine Referenz ( T&oder T&&) ist ein spezielles Element in der C ++ - Sprache. Es ermöglicht die Manipulation eines Objekts durch Referenz und hat spezielle Anwendungsfälle in der Sprache. Beispielsweise können Sie keinen Standardcontainer erstellen, der Referenzen enthält: Er vector<T&>ist fehlerhaft und generiert einen Kompilierungsfehler.

A std::reference_wrapperhingegen ist ein C ++ - Objekt, das eine Referenz enthalten kann. Als solches können Sie es in Standardcontainern verwenden.

std::refist eine Standardfunktion, die ein std::reference_wrapperArgument zurückgibt . In der gleichen Idee, std::crefkehrt std::reference_wrapperzu einer const - Referenz.

Eine interessante Eigenschaft von a std::reference_wrapperist, dass es eine hat operator T& () const noexcept;. Das heißt, selbst wenn es sich um ein echtes Objekt handelt , kann es automatisch in die Referenz konvertiert werden, die es enthält. So:

  • Da es sich um ein kopierzuweisbares Objekt handelt, kann es in Containern oder in anderen Fällen verwendet werden, in denen Referenzen nicht zulässig sind
  • Dank seiner Funktion operator T& () const noexcept;kann es überall dort verwendet werden, wo Sie eine Referenz verwenden können, da es automatisch in diese konvertiert wird.
Serge Ballesta
quelle
1
vor allem deshalb positiv bewertet, weil erwähnt wurde, operator T& ()welche anderen 2 Antworten nicht erwähnt wurden.
Metablaster