Différence C ++ entre std :: ref (T) et T &?

92

J'ai quelques questions concernant ce programme:

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

La sortie est:

false

Je veux savoir, si std::refne renvoie pas la référence d'un objet, alors que fait-il? En gros, quelle est la différence entre:

T x;
auto r = ref(x);

et

T x;
T &y = x;

Aussi, je veux savoir pourquoi cette différence existe? Pourquoi avons-nous besoin std::refou std::reference_wrapperquand nous avons des références (ie T&)?

CppNITR
la source
2
Possible duplication de Comment tr1 :: reference_wrapper est-il utile?
anderas
Indice: que se passe-t-il si vous faites x = y; dans les deux cas?
juanchopanza
2
En plus de mon drapeau en double (dernier commentaire): Voir par exemple stackoverflow.com/questions/31270810/... et stackoverflow.com/questions/26766939/…
anderas
2
@anderas ce n'est pas une question d'utilité, c'est principalement une question de différence
CppNITR
@CppNITR Ensuite, voyez les questions que j'ai liées quelques secondes avant votre commentaire. Surtout le second est utile.
anderas

Réponses:

91

Well refconstruit un objet du reference_wrappertype approprié pour contenir une référence à un objet. Ce qui signifie que lorsque vous postulez:

auto r = ref(x);

Cela renvoie a reference_wrapperet non une référence directe à x(ie T&). Ceci reference_wrapper(c'est-à-dire r) tient plutôt T&.

A reference_wrapperest très utile lorsque vous voulez émuler un referenceobjet qui peut être copié (il est à la fois constructible et assignable par copie ).

En C ++, une fois que vous créez une référence ( par exemple y) à un objet ( par exemple x), puis yet xpartager la même adresse de base . De plus, yne peut faire référence à aucun autre objet. De plus, vous ne pouvez pas créer un tableau de références, c'est-à-dire qu'un code comme celui-ci générera une erreur:

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

Cependant, c'est légal:

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

En parlant de votre problème avec cout << is_same<T&,decltype(r)>::value;, la solution est:

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

Laissez-moi vous montrer un programme:

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

Voyez ici, nous apprenons à connaître trois choses:

  1. Un reference_wrapperobjet (ici r) peut être utilisé pour créer un tableau de références qui n'était pas possible avec T&.

  2. ragit en fait comme une véritable référence (voir comment a r.get()=70changé la valeur de y).

  3. rn'est pas le même que T&mais r.get()est. Cela signifie que rtient T&ie comme son nom l'indique est un wrapper autour d'une référence T& .

J'espère que cette réponse est plus que suffisante pour expliquer vos doutes.

Ankit Acharya
la source
3
1: Non, a reference_wrapperpeut être réaffecté , mais il ne peut pas "contenir une référence à plusieurs objets". 2/3: Juste point sur l'endroit où .get()est approprié - mais sans suffixe r peut être utilisé de la même manière que T&dans les cas où rla conversion de s operatorpeut être invoquée sans ambiguïté - donc pas besoin d'appeler .get()dans de nombreux cas, y compris plusieurs dans votre code (ce qui est difficile à lire faute de place).
underscore_d
@underscore_d reference_wrapperpeut contenir un tableau de références si vous n'êtes pas sûr, vous pouvez l'essayer vous-même. Plus .get()est utilisé lorsque vous souhaitez modifier la valeur de l'objet qu'il reference_wrapperdétient, c'est r=70-à- dire illégal, vous devez donc l'utiliser r.get()=70. Essayez vous-même !!!!!!
Ankit Acharya
1
Pas vraiment. Premièrement, utilisons un libellé précis. Vous montrez un reference_wrapper contenant une référence à un tableau - pas un reference_wrapper qui contient lui-même "référence à plus d'un objets" . Le wrapper ne contient qu'une seule référence. Deuxièmement, je peux très bien obtenir une référence native à un tableau - êtes-vous sûr de ne pas simplement oublier les (parenthèses) int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper n'est pas spécial ici depuis natif T& fait le travail.
underscore_d
1
@AnkitAcharya Oui :-) mais pour être précis, résultat efficace mis à part, lui - même ne fait référence qu'à un seul objet. Quoi qu'il en soit, vous avez bien sûr raison de dire que contrairement à un ref normal, le wrapperpeut aller dans un conteneur. C'est pratique, mais je pense que les gens interprètent à tort cela comme étant plus avancé qu'il ne l'est en réalité. Si je veux un tableau de 'refs', je saute généralement l'intermédiaire avec vector<Item *>, ce qui wrapperse résume à ... et j'espère que les puristes anti-pointeurs ne me trouveront pas. Les cas d'utilisation convaincants sont différents et plus complexes.
underscore_d
1
"Un reference_wrapper est très utile lorsque vous souhaitez émuler une référence d'un objet qui peut être copié." Mais cela ne va-t-il pas à l'encontre du but d'une référence? Vous parlez de la réalité. Voilà ce qu'est une référence. Une référence qui peut être copiée semble inutile, car si vous voulez la copier, vous ne voulez pas de référence en premier lieu, vous devez simplement copier l'objet d'origine. Cela semble être une couche de complexité inutile pour résoudre un problème qui n'a pas besoin d'exister en premier lieu.
STU
53

std::reference_wrapper est reconnu par les installations standard pour pouvoir transmettre des objets par référence dans des contextes de passage par valeur.

Par exemple, std::bindpeut prendre le std::ref()vers quelque chose, le transmettre par valeur et le décompresser dans une référence plus tard.

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

Cet extrait de code affiche:

10
20

La valeur de ia été stockée (prise par valeur) f1au point où elle a été initialisée, mais f2a conservé une std::reference_wrappervaleur par, et se comporte donc comme si elle avait pris un int&.

Quentin
la source
2
@CppNITR bien sûr! Donnez-moi un moment pour assembler une petite démo :)
Quentin
1
qu'en est-il de la différence entre T & & ref (T)
CppNITR
3
@CppNITR std::ref(T)renvoie un std::reference_wrapper. C'est un peu plus qu'un pointeur enveloppé, mais il est reconnu par la bibliothèque comme "hé, je suis censé être une référence! S'il vous plaît, remettez-moi en un une fois que vous avez fini de me passer".
Quentin
38

Une référence ( T&ou T&&) est un élément spécial du langage C ++. Il permet de manipuler un objet par référence et a des cas d'utilisation particuliers dans le langage. Par exemple, vous ne pouvez pas créer un conteneur standard pour contenir des références: vector<T&>est mal formé et génère une erreur de compilation.

A std::reference_wrapperd'autre part est un objet C ++ capable de contenir une référence. En tant que tel, vous pouvez l'utiliser dans des conteneurs standard.

std::refest une fonction standard qui renvoie un std::reference_wrappersur son argument. Dans la même idée, std::crefretourne std::reference_wrapperà une référence const.

Une propriété intéressante de a std::reference_wrapper, est qu'il a un operator T& () const noexcept;. Cela signifie que même s'il s'agit d'un véritable objet , il peut être automatiquement converti en référence qu'il contient. Donc:

  • comme il s'agit d'un objet assignable par copie, il peut être utilisé dans des conteneurs ou dans d'autres cas où les références ne sont pas autorisées
  • grâce à son operator T& () const noexcept;, il peut être utilisé partout où vous pouvez utiliser une référence, car il y sera automatiquement converti.
Serge Ballesta
la source
1
voté principalement parce que la mention des operator T& ()2 autres réponses n'a pas été mentionnée.
metablaster