Differenza tra std :: reference_wrapper e simple pointer?


99

Perché è necessario avere std::reference_wrapper? Dove dovrebbe essere utilizzato? In cosa è diverso da un semplice puntatore? Come le sue prestazioni si confrontano con un semplice puntatore?


4
È fondamentalmente un puntatore che usi .con invece di->
MM

5
@MM No, l'utilizzo .non funziona nel modo che suggerisci (a meno che a un certo punto la proposta di punto dell'operatore non venga adottata e integrata :))
Columbo

3
Sono domande come questa che mi rendono infelice quando devo lavorare con il nuovo C ++.
Nils

Per seguire Columbo, std :: reference_wrapper viene utilizzato con la sua get()funzione membro o con la sua conversione implicita nel tipo sottostante.
Max Barraclough

Risposte:


88

std::reference_wrapperè utile in combinazione con i modelli. Avvolge un oggetto memorizzando un puntatore ad esso, consentendo la riassegnazione e la copia mentre imita la sua solita semantica. Indica inoltre ad alcuni modelli di libreria di memorizzare i riferimenti invece degli oggetti.

Considera gli algoritmi nell'STL che copiano i funtori: puoi evitare quella copia semplicemente passando un wrapper di riferimento che si riferisce al funtore invece del funtore stesso:

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

Funziona perché ...

  • ... reference_wrappers sovraccarico inoperator() modo che possano essere chiamati proprio come gli oggetti funzione a cui si riferiscono:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • ... (non) come i riferimenti ordinari, la copia (e l'assegnazione) reference_wrappersassegna solo le punte.

    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>
    

Copiare un wrapper di riferimento è praticamente equivalente a copiare un puntatore, il che è il più economico possibile. Tutte le chiamate di funzione inerenti al suo utilizzo (ad esempio quelle a operator()) dovrebbero essere solo inline in quanto sono one-line.

reference_wrappervengono creati tramite std::refestd::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'argomento template specifica il tipo e la qualifica cv dell'oggetto a cui si fa riferimento; r2si riferisce a const inte fornirà solo un riferimento a const int. Chiamate a wrapper di riferimento con constfuntori in essi chiameranno solo constfunzioni membro operator()s.

Gli inizializzatori Rvalue non sono consentiti, poiché consentirli farebbe più male che bene. Poiché rvalues ​​verrebbe spostato comunque (e con l' elisione della copia garantita anche in parte evitata), non miglioriamo la semantica; possiamo però introdurre puntatori pendenti, poiché un wrapper di riferimento non prolunga la durata della punta.

Interazione con la libreria

Come accennato in precedenza, si può istruire make_tuplea memorizzare un riferimento nel risultato tuplepassando l'argomento corrispondente tramite a 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&>

Nota che questo differisce leggermente da forward_as_tuple: Qui, rvalues ​​come argomenti non sono consentiti.

std::bindmostra lo stesso comportamento: non copia l'argomento ma memorizza un riferimento se è un file reference_wrapper. Utile se quell'argomento (o il funtore!) Non deve essere copiato ma rimane nell'ambito mentre bindviene utilizzato il -functor.

Differenza dai normali puntatori

  • Non esiste un livello aggiuntivo di riferimento indiretto sintattico. I puntatori devono essere dereferenziati per ottenere un valore per l'oggetto a cui si riferiscono; reference_wrapperhanno un operatore di conversione implicito e possono essere chiamati come l'oggetto che racchiudono.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers, a differenza dei puntatori, non hanno uno stato nullo. Devono essere inizializzati con un riferimento o un altroreference_wrapper .

    std::reference_wrapper<int> r; // Invalid
  • Una somiglianza è la semantica della copia superficiale: i puntatori e le reference_wrappers possono essere riassegnati.


In qualche modo è std::make_tuple(std::ref(i));superiore a std::make_tuple(&i);?
Laurynas Lazauskas

6
@LaurynasLazauskas È diverso. Quest'ultimo che hai mostrato salva un puntatore a i, non un riferimento ad esso.
Columbo

Hm ... immagino di non riuscire ancora a distinguere questi due come vorrei ... beh, grazie.
Laurynas Lazauskas

@Columbo Come è possibile un array di wrapper di riferimento se non hanno uno stato nullo? Gli array di solito non iniziano con tutti gli elementi impostati sullo stato nullo?
anatolyg

2
@anatolyg Cosa ti impedisce di inizializzare quell'array?
Columbo

27

Ci sono almeno due scopi motivanti di std::reference_wrapper<T>:

  1. Serve a fornire la semantica di riferimento agli oggetti passati come parametro valore ai modelli di funzione. Ad esempio, potresti avere un oggetto funzione di grandi dimensioni che desideri passare a std::for_each()cui prende il parametro dell'oggetto funzione per valore. Per evitare di copiare l'oggetto, puoi usare

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

    Il passaggio di argomenti std::reference_wrapper<T>a std::bind()un'espressione è abbastanza comune per associare gli argomenti per riferimento piuttosto che per valore.

  2. Quando si utilizza an std::reference_wrapper<T>con std::make_tuple()l'elemento tupla corrispondente diventa a T&anziché a T:

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

Puoi fornire un esempio di codice per il primo caso?
user1708860

1
@ user1708860: intendi diverso da quello dato ...?
Dietmar Kühl

Intendo il codice effettivo che va con std :: ref (fun) perché non capisco come viene usato (a meno che il divertimento non sia un oggetto e non una funzione ...)
user1708860

2
@ user1708860: sì, molto probabilmente funè un oggetto funzione (cioè un oggetto di una classe con un operatore di chiamata di funzione) e non una funzione: se funcapita di essere una funzione effettiva, std::ref(fun)non hanno scopo e rendono il codice potenzialmente più lento.
Dietmar Kühl

23

Un'altra differenza, in termini di codice auto-documentante, è che l'utilizzo di un reference_wrapperessenzialmente nega la proprietà dell'oggetto. Al contrario, un unique_ptrafferma la proprietà, mentre un puntatore nudo potrebbe o non potrebbe essere di proprietà (non è possibile saperlo senza guardare un sacco di codice correlato):

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

3
a meno che non sia codice pre-c ++ 11, il primo esempio dovrebbe implicare valori facoltativi, non posseduti, ad esempio per una ricerca nella cache basata sull'indice. Sarebbe bello se std ci fornisse qualcosa di standard per rappresentare un valore di proprietà non nullo (varianti uniche e condivise)
Bwmat

Forse non è così importante in C ++ 11, dove i puntatori nudi saranno quasi sempre presi in prestito comunque valori.
Elling il

reference_wrapperè superiore ai puntatori grezzi non solo perché è chiaro che non è proprietario, ma anche perché non può essere nullptr(senza imbrogli) e quindi gli utenti sanno che non possono passare nullptr(senza imbrogli) e tu sai che non devi verificalo.
underscore_d

19

Puoi pensarlo come un comodo involucro attorno ai riferimenti in modo da poterli utilizzare nei contenitori.

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

È fondamentalmente una CopyAssignableversione di T&. Ogni volta che vuoi un riferimento, ma deve essere assegnabile, utilizzare std::reference_wrapper<T>o la sua funzione di supporto std::ref(). Oppure usa un puntatore.


Altre stranezze 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

E confronto:

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

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

1
@LaurynasLazauskas Si potrebbe chiamare direttamente gli oggetti funzione contenuti nel wrapper. Anche questo è spiegato nella mia risposta.
Columbo

2
Poiché l'implementazione di riferimento è solo un puntatore all'interno, non riesco a capire perché i wrapper aggiungano riferimenti indiretti o penalità alle prestazioni
Riga

4
Non dovrebbe essere più indiretto di un semplice riferimento quando si tratta di un codice di rilascio
Riga

3
Mi aspetto che il compilatore incorpori il reference_wrappercodice banale , rendendolo identico al codice che utilizza un puntatore o un riferimento.
David Stone,

4
@LaurynasLazauskas: std::reference_wrapperha la garanzia che l'oggetto non è mai nullo. Considera un membro della classe std::vector<T *>. Devi esaminare tutto il codice della classe per vedere se questo oggetto può mai memorizzare un nullptrnel vettore, mentre con std::reference_wrapper<T>, hai la garanzia di avere oggetti validi.
David Stone,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.