2011-09-29 10 views
8

Credo che questa domanda è meglio chiese con un piccolo frammento di codice che ho appena scritto:Cosa succede in C++ quando passo un oggetto per riferimento e non rientra nell'ambito?

#include <iostream> 

using namespace std; 

class BasicClass 
{ 
public: 
    BasicClass() 
    { 
    } 
    void print() 
    { 
     cout << "I'm printing" << endl; 
    } 
}; 

class FriendlyClass 
{ 
public: 
    FriendlyClass(BasicClass& myFriend) : 
     _myFriend(myFriend) 
    { 
    } 
    void printFriend() 
    { 
     cout << "Printing my friend: "; 
     _myFriend.print(); 
    } 
private: 
    BasicClass& _myFriend; 
}; 

int main(int argv, char** argc) 
{ 
    FriendlyClass* fc; 
    { 
     BasicClass bc; 
     fc = new FriendlyClass(bc); 
     fc->printFriend(); 
    } 
    fc->printFriend(); 
    delete fc; 
    return 0; 
} 

Il codice compila e funziona bene con g ++:

$ g++ test.cc -o test 
$ ./test 
Printing my friend: I'm printing 
Printing my friend: I'm printing 

Tuttavia, questo non è il comportamento che ero aspettando. Mi aspettavo un qualche tipo di errore durante la seconda chiamata a fc->printFriend(). La mia comprensione di come il passaggio/memorizzazione per riferimento funziona in modo errato o si tratta di qualcosa che funziona solo su piccola scala e probabilmente esploderebbe in un'applicazione più sofisticata?

+3

Non ti piace quando tutti gridiamo "comportamento indefinito!" all'unisono? 8v) –

+0

"Mi aspettavo una sorta di errore al secondo". Basta rendere print() virtuale o memorizzare il testo che si sta stampando come variabile membro std :: string, e si otterrà il tuo errore. – SigTerm

+1

In genere non si desidera referenziare come membri dati in C++. Probabilmente stai venendo da uno sfondo Java. I riferimenti C++ non sono come riferimenti Java. Se vuoi qualcosa come un riferimento Java, usa un 'std :: shared_ptr ' e crea l'oggetto sull'heap. – fredoverflow

risposta

11

Funziona esattamente come per i puntatori: l'utilizzo di qualcosa (puntatore/riferimento) che fa riferimento a un oggetto che non esiste più è un comportamento non definito. Potrebbe sembrare che funzioni, ma può rompersi in qualsiasi momento.

Avviso: ciò che segue è una rapida spiegazione del motivo per cui tali chiamate di metodo possono sembrare funzionare in diverse occasioni, solo a scopo informativo; quando si scrive codice vero e proprio si dovrebbe fare affidamento solo su ciò che lo standard dice

Per quanto riguarda il comportamento che si sta osservando: sulla maggior parte (tutti?) compilatori chiamate di metodo sono implementate come chiamate di funzione con una nascosta this parametro che si riferisce alla istanza della classe su cui il metodo sta per operare. Ma nel tuo caso, il puntatore this non viene utilizzato affatto (il codice nella funzione non si riferisce a nessun campo e non c'è dispacciamento virtuale), quindi il puntatore (ora non valido) this non viene utilizzato e la chiamata ha esito positivo .

In altri casi può sembrare che funzioni anche se si riferisce a un oggetto fuori ambito perché la sua memoria non è stata ancora riutilizzata (sebbene il distruttore sia già stato eseguito, quindi il metodo troverà probabilmente l'oggetto in uno stato incoerente).

Ancora una volta, non dovresti fare affidamento su queste informazioni, ma solo per farti sapere perché questa chiamata funziona ancora.

1

Comportamento non definito. Per definizione non puoi fare ipotesi su cosa accadrà quando il codice verrà eseguito. Il compilatore potrebbe non cancellare la memoria dove risiede lo bc, ma non puoi contare su di esso.

Ho effettivamente corretto lo stesso bug in un programma al lavoro una volta. Quando si utilizzava il compilatore Intel, la variabile che era uscita dall'ambito non era ancora stata "ripulita", quindi la memoria era ancora "valida" (ma il comportamento non era definito). Il compilatore di Microsoft tuttavia lo ripuliva in modo più aggressivo e il bug era ovvio.

12

Quando si memorizza un riferimento a un oggetto che ha terminato la sua durata, l'accesso è un comportamento non definito. Quindi tutto può succedere, può funzionare, può fallire, può andare in crash, e come sembra mi piace dire che può ordinare una pizza.

+1

Può pagare per la pizza? ;) –

+1

@ R.MartinhoFernandes: Sarebbe un comportamento conforme agli standard! –

+1

@R. Martinho Fernandes: Sì, ma non è necessario. Non puoi inoltrarti su questo dal suo comportamento indefinito. –

1

Si dispone di un riferimento ciondolante, che provoca un comportamento non definito.

1
  1. un'occhiata qui: Can a local variable's memory be accessed outside its scope?

  2. Sarà quasi funzionerà, dal momento che ci sono funzioni virtuali, e non si acces campi di BasicClass: tutti i metodi chiamati presentano binding statico, e 'questo 'non viene mai utilizzato, in modo da non accedere mai effettivamente alla "memoria non allocata".

Problemi correlati