web-dev-qa-db-fra.com

Différence entre std :: reference_wrapper et un simple pointeur?

Pourquoi est-il nécessaire d'avoir std::reference_wrapper ? Où devrait-il être utilisé? En quoi est-ce différent d'un simple pointeur? Comment ses performances se comparent à un simple pointeur?

80

std::reference_wrapper Est utile en combinaison avec des modèles. Il encapsule un objet en stockant un pointeur sur celui-ci, ce qui permet de le réaffecter et de le copier tout en imitant sa sémantique habituelle. Il indique également à certains modèles de bibliothèque de stocker des références plutôt que des objets.

Considérez les algorithmes de la STL qui copient les foncteurs: Vous pouvez éviter cette copie en passant simplement un wrapper de référence faisant référence au foncteur au lieu du foncteur lui-même:

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

Cela fonctionne parce que…

  • reference_wrapper S surcharge operator() afin qu'ils puissent être appelés exactement comme les objets fonction auxquels ils font référence:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • … (Un) comme des références ordinaires, copier (et assigner) reference_wrappers Ne fait qu'attribuer la pointee.

    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>
    

Copier un wrapper de référence équivaut pratiquement à copier un pointeur, qui est aussi bon marché que possible. Tous les appels de fonction inhérents à son utilisation (par exemple, les uns à operator()) doivent être simplement en ligne car ils sont à une ligne.

reference_wrapper Sont créés via std::ref Et 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>

L'argument template spécifie le type et la qualification cv de l'objet auquel il est fait référence. r2 Fait référence à un const int Et ne donnera qu'une référence à const int. Les appels aux référenceurs contenant des foncteurs const n'appelleront que const fonction membre operator() s.

Les initialiseurs de valeur ne sont pas autorisés, leur permettre de faire plus de mal que de bien. Puisque les valeurs seraient déplacées de toute façon (et avec élision de copie garantie même en partie évitées), nous n'améliorons pas la sémantique; nous pouvons toutefois introduire des pointeurs en suspens, car un wrapper de référence ne prolonge pas la durée de vie de la pointee.

Interaction de la bibliothèque

Comme mentionné précédemment, on peut demander à make_Tuple De stocker une référence dans le Tuple résultant en passant l'argument correspondant par un 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&>

Notez que ceci diffère légèrement de forward_as_Tuple: Ici, les valeurs rvalues ​​en tant qu'arguments ne sont pas autorisées.

std::bind a le même comportement: il ne copie pas l'argument, mais stocke une référence s'il s'agit d'un reference_wrapper. Utile si cet argument (ou le foncteur!) N'a pas besoin d'être copié mais reste dans la portée tant que le foncteur bind- est utilisé.

Différence avec les pointeurs ordinaires

  • Il n'y a pas de niveau supplémentaire d'indirection syntaxique. Les pointeurs doivent être déréférencés pour obtenir une valeur pour l'objet auquel ils font référence; reference_wrapper A un implicite opérateur de conversion et peut être appelé comme l’objet qu’il enveloppe.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrapper, Contrairement aux pointeurs, n'a pas d'état nul. Ils doivent être initialisés avec soit une référence, soit un autre reference_wrapper .

    std::reference_wrapper<int> r; // Invalid
    
  • Une similitude est la sémantique de la copie superficielle: les pointeurs et les reference_wrapper Peuvent être réaffectés.

80
Columbo

Il y a au moins deux raisons motivant de std::reference_wrapper<T>:

  1. Il consiste à donner une sémantique de référence aux objets transmis en tant que paramètre de valeur aux modèles de fonction. Par exemple, vous pouvez avoir un objet de fonction volumineux que vous souhaitez transmettre à std::for_each(), qui prend son paramètre d'objet de fonction par valeur. Pour éviter de copier l'objet, vous pouvez utiliser

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

    Passer des arguments en tant que std::reference_wrapper<T> À une expression std::bind() est assez courant pour lier des arguments par référence plutôt que par valeur.

  2. Lorsque vous utilisez un std::reference_wrapper<T> Avec std::make_Tuple(), l'élément Tuple correspondant devient un T& Plutôt qu'un T:

    T object;
    f(std::make_Tuple(1, std::ref(object)));
    
23
Dietmar Kühl

Une autre différence, en termes de code auto-documenté, est que l'utilisation d'un reference_wrapper désavoue essentiellement la propriété de l'objet. En revanche, un unique_ptr affirme la propriété, alors qu'un pointeur nu peut appartenir ou non à la propriété (il est impossible de le savoir sans regarder beaucoup de code connexe):

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
18
Edward Loper

Vous pouvez penser à cela comme à un emballage pratique de références afin que vous puissiez les utiliser dans des conteneurs.

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

Il s’agit d’une version CopyAssignable de T&. Chaque fois que vous voulez une référence, mais que celle-ci soit assignable, utilisez std::reference_wrapper<T> Ou sa fonction d'assistance std::ref(). Ou utilisez un pointeur.


Autres bizarreries: 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

Et comparaison:

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

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