C ++ Różnica między std :: ref (T) a T &?

92

Mam kilka pytań dotyczących tego programu:

#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;
}

Wynik to:

false

Chcę wiedzieć, jeśli std::refnie zwraca odwołania do obiektu, to co robi? Zasadniczo jaka jest różnica między:

T x;
auto r = ref(x);

i

T x;
T &y = x;

Chcę też wiedzieć, dlaczego istnieje ta różnica? Dlaczego potrzebujemy std::reflub std::reference_wrapperkiedy mamy referencje (tj. T&)?

CppNITR
źródło
Wskazówka: co się stanie, jeśli zrobisz x = y; w obu przypadkach?
juanchopanza
2
Oprócz mojej zduplikowanej flagi (ostatni komentarz): Zobacz na przykład stackoverflow.com/questions/31270810/… i stackoverflow.com/questions/26766939/…
anderas
2
@anderas nie chodzi o użyteczność, chodzi głównie o różnicę
CppNITR
@CppNITR Następnie zobacz pytania, do których dołączyłem kilka sekund przed Twoim komentarzem. Szczególnie ten drugi jest przydatny.
anderas

Odpowiedzi:

91

Well refkonstruuje obiekt odpowiedniego reference_wrappertypu, aby przechowywać odwołanie do obiektu. Co oznacza, że ​​kiedy aplikujesz:

auto r = ref(x);

Zwraca reference_wrappera nie bezpośrednie odwołanie do x(tj T&.). To reference_wrapper(tj. r) Zamiast tego utrzymuje T&.

A reference_wrapperjest bardzo przydatne, gdy chcesz emulować a referenceobiektu, który można skopiować (można go zarówno konstruować, jak i przypisywać do kopii ).

W C ++, po utworzeniu odniesienia (powiedzmy y) do obiektu (powiedzmy x), to yi xdzielić ten sam adres bazowy . Ponadto ynie może odnosić się do żadnego innego przedmiotu. Nie możesz również utworzyć tablicy referencji, tj. Kod taki jak ten spowoduje błąd:

#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;
}

Jednak jest to zgodne z prawem:

#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
*/

Mówiąc o Twoim problemie cout << is_same<T&,decltype(r)>::value;, rozwiązaniem jest:

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

Pokażę wam program:

#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
*/

Tutaj poznajemy trzy rzeczy:

  1. reference_wrapperPrzedmiotu (tutaj r) mogą być wykorzystywane do tworzenia szereg odnośników , których nie było możliwe T&.

  2. rfaktycznie działa jak prawdziwe odniesienie (zobacz, jak r.get()=70zmieniła się wartość y).

  3. rto nie to samo, co T&ale r.get()jest. Oznacza to, że rhold T&ie, jak sama nazwa wskazuje, jest opakowaniem wokół referencji T& .

Mam nadzieję, że ta odpowiedź jest więcej niż wystarczająca, aby wyjaśnić Twoje wątpliwości.

Ankit Acharya
źródło
3
1: Nie, reference_wrappermożna ponownie przypisać element a , ale nie może on „utrzymywać odniesienia do więcej niż jednego obiektu”. 2/3: Słuszna uwaga na temat tego, gdzie .get()jest właściwe - ale bez sufiksu r można użyć tak samo jak T&w przypadkach, gdy rkonwersja operatormoże zostać wywołana jednoznacznie - więc nie ma potrzeby wywoływania .get()w wielu przypadkach, w tym kilku w kodzie (co jest trudne do odczytania ze względu na brak miejsca).
underscore_d
@underscore_d reference_wrappermoże przechowywać tablicę odniesień, jeśli nie masz pewności, możesz spróbować samemu. Plus .get()jest używany, gdy chcesz zmienić wartość obiektu, który reference_wrappertrzyma, tj. r=70Jest nielegalny, więc musisz go użyć r.get()=70. Spróbuj sam !!!!!!
Ankit Acharya
1
Nie całkiem. Najpierw użyjmy dokładnego sformułowania. Pokazujesz Reference_wrapper przechowujący odniesienie do tablicy - a nie Reference_wrapper, który sam zawiera „odniesienie do więcej niż jednego obiektu” . Opakowanie zawiera tylko jedno odniesienie. Po drugie, mogę uzyskać natywne odniesienie do tablicy - czy na pewno nie zapomniałeś po prostu (nawiasów) wokół int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper nie jest wyjątkowy, ponieważ tutaj rodzimy T& wykonuje pracę.
underscore_d
1
@AnkitAcharya Tak :-), ale żeby być precyzyjnym, pomijając efekt skuteczny , sam odnosi się tylko do jednego obiektu. W każdym razie masz oczywiście rację, że w przeciwieństwie do zwykłego ref, wrapperpuszka może być umieszczona w pojemniku. Jest to przydatne, ale myślę, że ludzie błędnie interpretują to jako bardziej zaawansowane niż w rzeczywistości. Jeśli chcę mieć tablicę „referencji”, zwykle pomijam pośredników vector<Item *>, do których wrappersprowadza się ... i mam nadzieję, że puryści przeciwni wskaźnikom mnie nie znajdą. Przekonujące przykłady użycia są różne i bardziej złożone.
underscore_d
1
„Reference_wrapper jest bardzo przydatny, gdy chcesz emulować odniesienie do obiektu, który można skopiować”. Ale czy to nie jest sprzeczne z celem odniesienia? Odnosisz się do faktycznej rzeczy. To właśnie JEST odniesienie. Odnośnik, który można skopiować, wydaje się bezcelowy, ponieważ jeśli chcesz go skopiować, to w pierwszej kolejności nie chcesz odniesienia, powinieneś po prostu skopiować oryginalny obiekt. Wydaje się, że jest to niepotrzebna warstwa złożoności, aby rozwiązać problem, który w ogóle nie musi istnieć.
stu
53

std::reference_wrapper jest rozpoznawany przez standardowe narzędzia jako zdolny do przekazywania obiektów przez odwołanie w kontekstach przekazujących wartość.

Na przykład, std::bindmoże przejąć std::ref()coś do czegoś, przesłać to według wartości, a później rozpakować z powrotem do odniesienia.

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();
}

Ten fragment kodu wyświetla:

10
20

Wartość izostała zapisana (przyjęta przez wartość) w f1punkcie, w którym została zainicjowana, ale f2zachowała std::reference_wrapperwartość według, a zatem zachowuje się tak, jakby przyjęła plik int&.

Quentin
źródło
2
@CppNITR pewnie! Daj mi chwilę na zebranie małej wersji demo :)
Quentin
1
a co z różnicą między T & & ref (T)
CppNITR
3
@CppNITR std::ref(T)zwraca plik std::reference_wrapper. To niewiele więcej niż owinięty wskaźnik, ale jest rozpoznawany przez bibliotekę jako „hej, mam być punktem odniesienia! Proszę, zamień mnie z powrotem w jednego, gdy skończysz mnie przekazywać”.
Quentin
38

Odniesienie ( T&lub T&&) to specjalny element w języku C ++. Pozwala na manipulowanie obiektem poprzez odniesienie i ma specjalne przypadki użycia w języku. Na przykład nie można utworzyć standardowego kontenera do przechowywania odniesień: vector<T&>jest źle sformułowany i generuje błąd kompilacji.

A std::reference_wrapperz drugiej strony jest obiektem C ++ zdolnym do przechowywania referencji. W związku z tym można go używać w standardowych pojemnikach.

std::refjest standardową funkcją, która zwraca a std::reference_wrapperw swoim argumencie. W tym samym pomyśle std::crefpowraca std::reference_wrapperdo odwołania do stałej.

Jedną z interesujących właściwości a std::reference_wrapperjest to, że ma rozszerzenie operator T& () const noexcept;. Oznacza to, że nawet jeśli jest to prawdziwy obiekt , można go automatycznie przekształcić w odniesienie, które posiada. Więc:

  • ponieważ jest to obiekt przypisywalny do kopii, może być używany w kontenerach lub w innych przypadkach, w których odniesienia nie są dozwolone
  • dzięki operator T& () const noexcept;niemu można go używać wszędzie tam, gdzie można użyć odniesienia, ponieważ zostanie na niego automatycznie przekonwertowany.
Serge Ballesta
źródło
1
Głosowano za pozytywnymi, głównie ze względu na wzmiankę o operator T& ()których 2 pozostałych odpowiedziach nie ma.
metablaster