[c++] Come mai un riferimento non const non può legarsi a un oggetto temporaneo?


Answers

Nel tuo codice getx() restituisce un oggetto temporaneo, un cosiddetto "rvalore". Puoi copiare rvalues ​​in oggetti (cioè variabili) o legarli a riferimenti const (che estenderanno la loro durata fino alla fine della vita del riferimento). Non è possibile associare rvalues ​​a riferimenti non const.

Questa è stata una decisione progettuale intenzionale al fine di impedire agli utenti di modificare accidentalmente un oggetto che sta per morire alla fine dell'espressione:

g(getx()); // g() would modify an object without anyone being able to observe

Se vuoi farlo, dovrai prima fare una copia locale o dell'oggetto o associarlo a un riferimento const:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Si noti che il prossimo standard C ++ includerà riferimenti rvalue. Ciò che conosci come riferimenti sta quindi diventando per essere chiamato "riferimenti lvalue". Ti sarà consentito legare i valori di rvalue ai riferimenti di rvalue e puoi sovraccaricare le funzioni su "rvalore-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

L'idea alla base dei riferimenti di valore è che, poiché questi oggetti stanno per morire comunque, puoi sfruttare questa conoscenza e implementare quella che viene chiamata "semantica del movimento", un certo tipo di ottimizzazione:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
Question

Perché non è possibile ottenere un riferimento non const a un oggetto temporaneo, quale funzione getx() restituisce? Chiaramente, questo è proibito da C ++ Standard, ma sono interessato allo scopo di tale restrizione, non un riferimento allo standard.

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. È chiaro che la durata dell'oggetto non può essere la causa, poiché il riferimento costante a un oggetto non è vietato da C ++ Standard.
  2. È chiaro che l'oggetto temporaneo non è costante nell'esempio sopra, poiché le chiamate a funzioni non costanti sono consentite. Ad esempio, ref() potrebbe modificare l'oggetto temporaneo.
  3. Inoltre, ref() ti permette di ingannare il compilatore e ottenere un collegamento a questo oggetto temporaneo e questo risolve il nostro problema.

Inoltre:

Dicono che "l'assegnazione di un oggetto temporaneo al riferimento const estende la durata di questo oggetto" e "Non si dice nulla sui riferimenti non-const". La mia domanda aggiuntiva Il seguente incarico estende la durata dell'oggetto temporaneo?

X& x = getx().ref(); // OK



Sembra che la domanda iniziale sul motivo per cui questo non è consentito ha avuto una risposta chiara: "perché è più probabile un errore".

FWIW, ho pensato di mostrare come si poteva fare, anche se non penso che sia una buona tecnica.

Il motivo per cui a volte voglio passare un metodo temporaneo a un metodo che utilizza un riferimento non const è quello di gettare intenzionalmente un valore restituito da riferimento che il metodo chiamante non interessa. Qualcosa come questo:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

Come spiegato nelle risposte precedenti, questo non viene compilato. Ma questo compila e funziona correttamente (con il mio compilatore):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

Questo mostra che puoi usare casting per mentire al compilatore. Ovviamente, sarebbe molto più pulito dichiarare e passare una variabile automatica non utilizzata:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

Questa tecnica introduce una variabile locale non necessaria nello scopo del metodo. Se per qualche motivo si desidera impedire che venga utilizzato in un secondo momento nel metodo, ad esempio per evitare confusione o errori, è possibile nasconderlo in un blocco locale:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

-- Chris




"È chiaro che l'oggetto temporaneo non è costante nell'esempio sopra, poiché sono consentite chiamate a funzioni non costanti, ad esempio ref () potrebbe modificare l'oggetto temporaneo."

Nel tuo esempio getX () non restituisce una const X così puoi chiamare ref () più o meno allo stesso modo in cui potresti chiamare X (). Ref (). Si sta restituendo un riferimento non const e quindi si possono chiamare metodi non const, ciò che non si può fare è assegnare il riferimento a un riferimento non const.

Insieme al commento di SadSidos, ciò rende errati i tre punti.




La soluzione alternativa è la parola chiave 'mutabile'. In realtà essere cattivi è lasciato come esercizio per il lettore. Oppure vedere qui: http://www.ddj.com/cpp/184403758




Perché è discusso nelle FAQ C ++ ( grassetto in mio):

In C ++, i riferimenti non const possono essere associati a lvalue e i riferimenti const possono essere associati a lvalue o rvalues, ma non c'è nulla che possa essere associato a un valore non const. Questo serve a proteggere le persone dal cambiare i valori dei beni temporanei che vengono distrutti prima che il loro nuovo valore possa essere usato . Per esempio:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

Se quel incr (0) fosse permesso o qualche temporaneo che nessuno avesse mai visto sarebbe stato incrementato o - molto peggio - il valore di 0 diventerebbe 1. Quest'ultimo sembra sciocco, ma in realtà c'era un bug simile nei primi compilatori di Fortran che a parte una posizione di memoria per contenere il valore 0.




Related