C ++ Restituzione del riferimento alla variabile locale


116

Il codice seguente (func1 ()) è corretto se deve restituire i? Ricordo di aver letto da qualche parte che c'è un problema quando si restituisce un riferimento a una variabile locale. In cosa differisce da func2 ()?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1
Se cambi func1 () per utilizzare la memoria allocata dinamicamente, sono gli stessi :-)int& i = * new int;
Martin York

Risposte:


193

Questo snippet di codice:

int& func1()
{
    int i;
    i = 1;
    return i;
}

non funzionerà perché stai restituendo un alias (un riferimento) a un oggetto con una durata limitata all'ambito della chiamata di funzione. Ciò significa che una volta func1()ritorna, int imuore, rendendo inutile il riferimento restituito dalla funzione perché ora si riferisce a un oggetto che non esiste.

int main()
{
    int& p = func1();
    /* p is garbage */
}

La seconda versione funziona perché la variabile è allocata nell'archivio gratuito, che non è vincolato alla durata della chiamata di funzione. Tuttavia, sei responsabile per deletel'assegnazione int.

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

Tipicamente inseriresti il ​​puntatore in qualche classe RAII e / o una funzione di fabbrica in modo da non doverlo fare da deletesolo.

In entrambi i casi, puoi semplicemente restituire il valore stesso (anche se mi rendo conto che l'esempio che hai fornito è stato probabilmente inventato):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

Si noti che è perfettamente corretto func3()restituire oggetti di grandi dimensioni nello stesso modo in cui restituisce valori primitivi perché quasi tutti i compilatori oggigiorno implementano una qualche forma di ottimizzazione del valore di ritorno :

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

È interessante notare che l'associazione di una temporanea a un riferimento const è perfettamente legale C ++ .

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2
Bella spiegazione. : hattip: Nel terzo frammento di codice, stai cancellando int* p = func2(); delete p;Ora, quando hai cancellato 'p', significa che anche la memoria allocata "all'interno" della func2()definizione della funzione è stata cancellata?
Aquarius_Girl

2
@ Anisha Kaul: Sì. La memoria è stata allocata all'interno func2()e rilasciata all'esterno nella riga successiva. È un modo piuttosto soggetto a errori per gestire la memoria, come ho detto che useresti invece qualche variante di RAII. A proposito, sembra che tu stia imparando il C ++. Consiglio di prendere un buon libro introduttivo C ++ da cui imparare. Inoltre, per riferimento futuro se hai una domanda, puoi sempre pubblicare la domanda su Stack Overflow. I commenti non sono pensati per porre domande completamente nuove.
In silico

Ora ho capito, l'hai fatto bene! La funzione restituiva un puntatore e, al di fuori di quella funzione, hai cancellato la memoria a cui puntava. Ora è chiaro e grazie per il collegamento.
Aquarius_Girl

e hai modificato la risposta ?? : mad: avrei potuto perderlo facilmente. ;);)
Aquarius_Girl

@ Anisha Kaul: No, non l'ho fatto. L'ultima volta che ho modificato la mia risposta è stato il 10 gennaio, secondo il timestamp sotto il mio post.
In silico

18

Una variabile locale è la memoria nello stack, quella memoria non viene automaticamente invalidata quando esci dall'ambito. Da una funzione più profonda annidata (più in alto nello stack in memoria), è perfettamente sicuro accedere a questa memoria.

Tuttavia, una volta che la funzione ritorna e finisce, le cose si fanno pericolose. Di solito la memoria non viene cancellata o sovrascritta quando torni, il che significa che la memoria a quell'indirizzo contiene ancora i tuoi dati - il puntatore sembra valido.

Fino a quando un'altra funzione crea lo stack e lo sovrascrive. Questo è il motivo per cui questo può funzionare per un po '- e poi improvvisamente cessa di funzionare dopo che un insieme di funzioni particolarmente profondamente annidate, o una funzione con dimensioni veramente enormi o molti oggetti locali, raggiunge di nuovo quella memoria di stack.

Può anche succedere di raggiungere nuovamente la stessa parte di programma e di sovrascrivere la vecchia variabile di funzione locale con la nuova variabile di funzione. Tutto ciò è molto pericoloso e dovrebbe essere fortemente scoraggiato. Non utilizzare puntatori a oggetti locali!


2

Una buona cosa da ricordare sono queste semplici regole e si applicano sia ai parametri che ai tipi restituiti ...

  • Valore: crea una copia dell'articolo in questione.
  • Puntatore: si riferisce all'indirizzo dell'articolo in questione.
  • Riferimento: è letteralmente l'oggetto in questione.

C'è un tempo e un luogo per ciascuno, quindi assicurati di conoscerli. Le variabili locali, come hai mostrato qui, sono proprio questo, limitate al tempo in cui sono attive localmente nell'ambito della funzione. Nel tuo esempio, avere un tipo di int*restituzione e restituzione &isarebbe stato ugualmente errato. In tal caso sarebbe meglio farlo ...

void func1(int& oValue)
{
    oValue = 1;
}

In questo modo cambierebbe direttamente il valore del parametro passato. Mentre questo codice ...

void func1(int oValue)
{
    oValue = 1;
}

non lo farei. Cambierà semplicemente il valore di oValuelocal alla chiamata di funzione. Il motivo è perché in realtà cambieresti solo una copia "locale" oValuee non oValuese stesso.

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.