2011-01-12 13 views
14

Qual è la logica di progettazione dietro permettendo a questaprolungando la durata di provvisori

const Foo& a = function_returning_Foo_by_value(); 

ma non questa

Foo& a = function_returning_Foo_by_value(); 

?

cosa potrebbe possibile andare storto nella seconda riga (che non sarebbe già andare male nella prima riga)?

+0

Non è la stessa domanda discussa da Herb Sutter qui http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/? – DumbCoder

+0

@DumbCoder: no, Herb Sutter progetta l'utilizzo dello standard C++ mentre Fred discute la logica dietro lo standard. –

risposta

4

Risponderò alla tua domanda ... il contrario.

perché hanno permesso Foo const& foo = fooByValue(); per cominciare?

rende la vita (un po ') più facile, ma introduce il potenziale comportamento non definito in tutto il luogo.

Foo const& fooByReference() 
{ 
    return fooByValue(); // error: returning a reference to a temporary 
} 

Questo è ovviamente sbagliato, e in effetti il ​​compilatore lo riferirà doverosamente. Come da commento di Tomalak: non è obbligatorio dallo standard, ma i buoni compilatori dovrebbero segnalarlo. Clang, gcc e MSVC fanno. Penso che anche Comeau e Icc lo farebbero.

Foo const& fooByIndirectReference() 
{ 
    Foo const& foo = fooByValue(); // OK, you're allowed to bind a temporary 
    return foo;     // Generally accepted 
} 

Questo è sbagliato, ma è più sottile. Il problema è che la durata del temporaneo è legata alla durata di foo, che esce dall'ambito alla fine della funzione. A copia di foo viene passato al chiamante e questa copia punta nell'etere.

Ho sollevato il bug su Clang, e Argyris è riuscito a diagnosticare questo caso (complimenti davvero: p).

Foo const& fooForwarder(Foo const&); // out of line implementation which forwards 
            // the argument 

Foo const& fooByVeryIndirectReference() 
{ 
    return fooForwarder(fooByValue()); 
} 

La temporanea creata da fooByValue è legata alla durata di vita l'argomento della fooForwarder, che doverosamente fornire una copia (di riferimento), copia che viene restituito al chiamante, anche se ora punta nell'etere .

Il problema qui è che l'implementazione di fooForwarder è perfettamente soddisfacente rispetto allo standard e tuttavia crea un comportamento non definito nel relativo chiamante.

Il fatto scoraggiante, tuttavia, è che la diagnosi di questo richiede la conoscenza dell'implementazione di fooForwarder, che è fuori dalla portata del compilatore.

L'unica soluzione che riesco a capire (a parte il WPA) è una soluzione runtime: ogni volta che un temporaneo è limitato a un riferimento, è necessario assicurarsi che il riferimento restituito non condivida lo stesso indirizzo ... e quindi che cosa ? assert? sollevare un'eccezione? E poiché è solo una soluzione runtime, non è chiaramente soddisfacente.

L'idea di associare un riferimento temporaneo a un riferimento è fragile.

+0

"Questo è ovviamente sbagliato, e infatti il ​​compilatore lo riferirà doverosamente" se sei fortunato, se hai una toolchain che lo fa, se i tuoi avvisi sono impostati su un certo livello, ecc. Ecc. La lingua C++ non richiede alcuna diagnostica per questo caso. –

+0

@Tomalak: correggerò, è segnalato da almeno MSVC, gcc e Clang, e penso che probabilmente anche Comeau e icc. –

0

"Cosa potrebbe andare storto" è che si modifica un oggetto allora immediatamente perdere i cambiamenti, e la regola è quindi definito per aiutare non fare tali errori. Potresti pensare che se richiamassi di nuovo la funzione otterresti un oggetto con le tue modifiche, che ovviamente non lo faresti perché hai modificato una copia.

Il caso tipico in cui si crea una temporanea poi chiama un metodo non-const su di esso è quando si sta per scambiarlo:

std::string val; 
some_func_that_returns_a_string().swap(val); 

Questo a volte può essere molto utile.

+0

Perché dovrei perdere le modifiche? Guarda il titolo della mia domanda, il temporaneo vivrà fino a "a", proprio come nel caso di "const Foo &'. – fredoverflow

3

La ragione che i puntatori non-const non prolungano la durata di provvisori è che i riferimenti non-const non può essere vincolata alla provvisori, in primo luogo.

ci sono molte ragioni per questo, mi limiterò a mostrare un esempio classico che coinvolge conversioni di ampliamento implicite:

struct Foo {}; 
bool CreateFoo(Foo*& result) { result = new Foo(); return true; } 

struct SpecialFoo : Foo {}; 
SpecialFoo* p; 
if (CreateFoo(p)) { /* DUDE, WHERE'S MY OBJECT! */ } 

Il razionale per permettere riferimenti const di impegnare provvisori è che consente al codice perfettamente ragionevole come questo :

Si noti che in questo caso non era necessaria alcuna estensione di durata.

+0

Il problema che questo codice presenta è che i valori non dovrebbero legarsi a riferimenti normali. Non mostra il motivo per cui i rvalues ​​dovrebbero invece legarsi a riferimenti const. Amare il ragazzo, dov'è il mio oggetto :) – Puppy

+1

@David: 'const Foo * &' non è un riferimento const. Intendevi 'Foo * const &'? –

+0

Il mio male! Dovrei stare più attento ... Ho avuto l'intuizione e ho provato, ma ho eseguito il test sbagliato. Hai ragione. Ho rimosso il commento in cui mi sono reso ridicolo :) +1 –

2

Ho compreso il razionale come segue: un temporaneo dovrebbe essere distrutto quando esce dal campo di applicazione. Se prometti di non modificarlo ti lascerò prolungare la sua vita.

Problemi correlati