Un membro della classe di riferimento const prolunga la vita di un temporaneo?


171

Perché questo:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

Dai un output di:

La risposta è:

Invece di:

La risposta è: quattro


39
E solo per più divertimento, se avessi scritto cout << "The answer is: " << Sandbox(string("four")).member << endl;, allora sarebbe garantito che funzionasse.

7
@RogerPate Potresti spiegare perché?
Paolo M,

16
Per qualcuno che è curioso, l'esempio pubblicato da Roger Pate funziona perché la stringa ("quattro") è temporanea e quel temporaneo viene distrutto alla fine dell'espressione completa , quindi nel suo esempio quando SandBox::memberviene letto, la stringa temporanea è ancora viva .
PcAF,

1
La domanda è: poiché la scrittura di tali classi è pericolosa, esiste un avvertimento del compilatore contro il passaggio temporaneo a tali classi o esiste una linea guida di progettazione (in Stroustroup?) Che vieta la scrittura di classi che memorizzano riferimenti? Sarebbe meglio una linea guida di progettazione per memorizzare i puntatori anziché i riferimenti.
Grim Fandango,

@PcAF: Potresti spiegare perché il temporaneo string("four")viene distrutto alla fine dell'espressione completa e non dopo l' Sandboxuscita del costruttore? La risposta di Potatoswatter dice che un vincolo temporaneo a un membro di riferimento nell'inizializzatore del ctor di un costruttore (§12.6.2 [class.base.init]) persiste fino a quando il costruttore non esce.
Taylor Nichols,

Risposte:


166

Solo i const riferimenti locali prolungano la durata della vita.

La norma specifica tale comportamento in §8.5.3 / 5, [dcl.init.ref], la sezione sugli inizializzatori delle dichiarazioni di riferimento. Il riferimento nel tuo esempio è legato all'argomento del costruttore ne diventa non valido quando l'oggetto nè destinato a uscire dall'ambito.

L'estensione di durata non è transitiva attraverso un argomento di funzione. §12.2 / 5 [class.temporary]:

Il secondo contesto è quando un riferimento è associato a un temporaneo. Il temporaneo a cui è associato il riferimento o il temporaneo che è l'oggetto completo di un oggetto secondario a cui è associato il temporaneo persiste per la durata del riferimento, ad eccezione di quanto specificato di seguito. Un temporaneo associato a un membro di riferimento nell'inizializzatore ctor di un costruttore (§12.6.2 [class.base.init]) persiste fino a quando il costruttore non esce. Un temporaneo associato a un parametro di riferimento in una chiamata di funzione (§5.2.2 [expr.call]) persiste fino al completamento dell'espressione completa contenente la chiamata.


49
Dovresti anche vedere GotW # 88 per una spiegazione più a misura d'uomo: herbsutter.com/2008/01/01/…
Nathan Ernst

1
Penso che sarebbe più chiaro se lo standard dicesse "Il secondo contesto è quando un riferimento è legato a un valore". Nel codice di OP potresti dire che memberè legato a un temporaneo, perché l'inizializzazione membercon nmezzi per legare memberallo stesso oggetto nè vincolata e che in realtà è un oggetto temporaneo in questo caso.
MM

2
@MM Ci sono casi in cui gli inizializzatori lvalue o xvalue contenenti un valore estenderanno il valore. Il mio documento di proposta P0066 esamina lo stato delle cose.
Potatoswatter,

1
A partire da C ++ 11, i riferimenti Rvalue prolungano anche la vita di un temporaneo senza richiedere un constquaifier.
GetFree

3
@KeNVinFavo sì, usare un oggetto morto è sempre UB
Potatoswatter

30

Ecco il modo più semplice per spiegare cosa è successo:

In main () hai creato una stringa e la hai passata al costruttore. Questa istanza di stringa esisteva solo all'interno del costruttore. All'interno del costruttore, è stato assegnato un membro per puntare direttamente a questa istanza. Quando quando l'ambito ha lasciato il costruttore, l'istanza di stringa è stata distrutta e il membro ha quindi puntato su un oggetto stringa che non esisteva più. Avere Sandbox.member che punta a un riferimento al di fuori del suo ambito non terrà tali istanze esterne nell'ambito.

Se si desidera correggere il programma per visualizzare il comportamento desiderato, apportare le seguenti modifiche:

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

Ora la temperatura passerà dall'ambito alla fine di main () anziché alla fine del costruttore. Tuttavia, questa è una cattiva pratica. La tua variabile membro non dovrebbe mai essere un riferimento a una variabile che esiste al di fuori dell'istanza. In pratica, non si sa mai quando quella variabile andrà fuori dal campo di applicazione.

Quello che consiglio è di definire Sandbox.member come const string member;Questo copierà i dati del parametro temporaneo nella variabile membro invece di assegnare la variabile membro come parametro temporaneo stesso.


Se lo faccio: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;funzionerà ancora?
Yves,

@Thomas const string &temp = string("four");dà lo stesso risultato di const string temp("four"); , a meno che non si usi decltype(temp)specificamente
MM

@MM Grazie mille ora capisco perfettamente questa domanda.
Yves,

However, this is bad practice.- perché? Se sia la temp che l'oggetto contenente utilizzano l'archiviazione automatica nello stesso ambito, non è sicuro al 100%? E se non lo fai, cosa faresti se la stringa fosse troppo grande e troppo costosa da copiare?
max

2
@max, perché la classe non impone il passaggio temporaneo per avere l'ambito corretto. Significa che un giorno potresti dimenticare questo requisito, passare un valore temporaneo non valido e il compilatore non ti avvertirà.
Alex Che

5

Dal punto di vista tecnico, questo programma non è necessario per inviare effettivamente nulla all'output standard (che è inizialmente un flusso bufferizzato).

  • Il cout << "The answer is: "bit verrà emesso "The answer is: "nel buffer di stdout.

  • Quindi il << sandbox.memberbit fornirà il riferimento penzolante in operator << (ostream &, const std::string &), che invoca un comportamento indefinito .

Per questo motivo, non è garantito che accada nulla. Il programma potrebbe funzionare apparentemente bene o potrebbe bloccarsi senza nemmeno scaricare lo stdout - il che significa che il testo "La risposta è:" non apparirà sullo schermo.


2
Quando c'è UB, il comportamento dell'intero programma è indefinito - non inizia solo in un punto particolare dell'esecuzione. Quindi non possiamo dire con certezza che "The answer is: "sarà scritto da nessuna parte.
Toby Speight,

0

Perché la tua stringa temporanea è uscita dall'ambito una volta che il costruttore Sandbox è tornato e lo stack da essa occupato è stato recuperato per altri scopi.

In generale, non si dovrebbe mai conservare riferimenti a lungo termine. I riferimenti sono utili per argomenti o variabili locali, mai membri della classe.


7
"Mai" è una parola terribilmente forte.
Fred Larson,

17
mai membri della classe a meno che non sia necessario mantenere un riferimento a un oggetto. Ci sono casi in cui è necessario conservare i riferimenti ad altri oggetti e non copie, per tali casi i riferimenti sono una soluzione più chiara dei puntatori.
David Rodríguez - dribeas,

0

ti riferisci a qualcosa che è svanito. Di seguito funzionerà

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
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.