2014-09-22 13 views
8

Anche se gli oggetti vengono passati alle funzioni mediante il normale meccanismo passaggio dei parametri chiamata per valore, che, in teoria, protegge ed isola l'argomento chiamata, è è comunque possibile che si verifichi un effetto lato che possa influire o addirittura danneggiare l'oggetto utilizzato come argomento . Ad esempio, se un oggetto utilizzato come argomento alloca la memoria e libera quella memoria quando viene distrutta, la sua copia locale all'interno della funzione libererà la stessa memoria quando il suo distruttore è chiamato . Ciò lascerà l'oggetto originale danneggiato ed efficacemente inutile.distruttore chiamato quando gli oggetti sono passati per valore

Questo è scritto in C++: The Complete Reference

In questo programma

#include<iostream> 

using namespace std; 

class Sample 
{   
public: 
     int *ptr; 
     Sample(int i) 
     { 
     ptr = new int(i); 
     } 
     ~Sample() 
     { 
     cout<<"destroyed"; 
     delete ptr; 
     } 
     void PrintVal() 
     { 
     cout << "The value is " << *ptr; 
     } 
}; 
void SomeFunc(Sample x) 
{ 
cout << "Say i am in someFunc " << endl; 
} 
int main() 
{ 
Sample s1= 10; 
SomeFunc(s1); 
s1.PrintVal(); 
} 

Esso genera un errore di runtime come s1 oggetto viene distrutto quando ritorna dall'oggetto. Non riuscivo a capire perché questo potrebbe accadere, dal momento che una copia avrebbe dovuto essere fatta. Ho pensato che forse era perché non c'era alcun costruttore di copia presente nella definizione della classe. Ma sono stato sorpreso di scoprire che se si usa questa funzione dichiarazione

void SomeFunc(Sample &x) 
{ 
cout << "Say i am in someFunc " << endl; 
} 

In questa dichiarazione non si verifica alcun errore. Non dovrebbe verificarsi l'errore anche qui perché è referenziato? Qualcuno può spiegare cosa succede in entrambi i casi.

+1

Sottolinei cosa passa per mezzo di riferimento? – doctorlove

+0

buon esempio del perché i costruttori di copia sono utili –

+2

@Claptrap Piuttosto un buon esempio del perché non si dovrebbe gestire la memoria da soli e lasciare che un puntatore intelligente lo faccia. Meglio della regola di tre/cinque è la [** regola di zero **] (http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html) – JBL

risposta

9

Questo è vero perché non hai fornito un costruttore di copie. Quindi il compilatore ne genererà uno per te, che fa una copia banale. E questa è la copia banale del puntatore che è problematico qui.

Per la seguente dichiarazione

void SomeFunc(Sample x); 

Ci sarà infatti una copia quando si passa s1 alla funzione, ma la copia avrà una copia del puntatore, cioè, i due oggetti punterà alla stesso int.

Poi, quando si esce la funzione, la copia sarà distrutto e sarà eliminare tale puntatore, lasciando l'oggetto originale nel codice chiamante con un puntatore appena eliminata (ricordate, essi indicano la stessa cosa).

Poi, per la seguente dichiarazione

void SomeFunc(Sample &x); 

non si dispone di alcuna copia, quindi il problema non si presenta. In effetti, passare per riferimento significa che all'interno della funzione, l'oggetto Sample che stai manipolando è esattamente lo stesso di quello che hai passato alla funzione, e non verrà distrutto quando la funzione viene chiusa.

+0

+1 Ottima risposta! Vale sempre la pena ricordare la [Regola del 3] (http://en.cppreference.com/w/cpp/language/rule_of_three) –

+1

[Regola 0] (https://blog.rmf.io/cxx11/rule -0-zero) sarebbe superiore se si può evitare la regola di 3 (o 5, post C++ 11) – JBL

0

vostro oggetto è stato copiato settore per settore, in modo da dentro SomeFunc si dispone di due istanze di Sample - s1 e x (ma solo x è accessibile), e il valore del x.ptr è pari a s1.ptr. Quindi quando termina SomeFunc, il distruttore viene chiamato e da quel punto s1.ptr punti alla memoria non allocata. Si chiama "puntatore ciondolante".

3

Darò una risposta in più dalla prospettiva di Modern C++ di "evitare puntatori grezzi se è possibile". Ma sarò anche sottolineare una distinzione importante che si dovrebbe essere a conoscenza di:

C++ constructor syntax

Ma in primo luogo, prendiamo in considerazione ciò che la vostra intenzione è. Se ho scritto:

Sample x = 1; 
Sample y = x; 

Quale dovrebbe essere la semantica?

Qualora i Sample "copie" hanno ciascuno il proprio 'ptr' indipendenti, la cui punta-di opporsi vita dura solo fino a quando la classe cui vivono?

In genere, non è necessario alcun puntatore se questo è ciò che si desidera.

maggior parte del tempo, la dimensione totale della classe sarà abbastanza ragionevole che l'allocazione di stack non sarà un problema se state dichiarando senza new(come tu sei qui). Quindi perché coinvolgere i puntatori? Basta usare int i (o qualunque sia la tua classe non POD).

Se effettivamente avere il tipo di caso in cui si fai necessità di allocare dinamicamente grandi blocchi di dati da gestire se stessi (vs. rimandando a C++ collezioni delle biblioteche o simili), quelli potrebbero exceed your stack. Se hai bisogno di allocare dinamicamente, avrai bisogno di copiare la costruzione in un modo o nell'altro. Ciò significa che Sample dovrà gestire in modo esplicito la costruzione della copia -oppure- utilizzare una classe smart pointer che la impone in modo che lo non sia necessario a.

Prima diciamo si sta tenendo il puntatore grezzo, che vorrebbe dire:

Sample(const Sample & other) 
{ 
    ptr = new int(*other.ptr); 
} 

MA si potrebbe ridurre il rischio di errori in questa situazione usando un unique_ptr invece. Un unique_ptr distruggerà i dati puntati dal puntatore raw che tiene automaticamente quando viene eseguito il suo distruttore. Quindi non devi preoccuparti di chiamare delete.

Anche, un unique_ptr si rifiuterà di copiare per impostazione predefinita. Quindi se hai appena scritto:

class Sample 
{   
public: 
    unique_ptr<int> ptr; 
    Sample(int i) 
    { 
     ptr = std::unique_ptr<int>(new int(i)); 
    } 
    ~Sample() 
    { 
     cout << "destroyed"; 
    } 
    void PrintVal() 
    { 
     cout << "The value is " << *ptr; 
    } 
}; 

La classe stessa può essere costruita, ma si otterrebbero degli errori nei callites. Faranno notare che stai facendo delle copie per qualcosa per cui la costruzione della copia non è stata definita correttamente. Non solo quello...non si sta solo facendo una copia nel vostro programma, ma due:

In function ‘int main()’: 
error: use of deleted function ‘Sample::Sample(const Sample&)’ 
Sample s1 = 10; 
      ^
note: ‘Sample::Sample(const Sample&)’ is implicitly deleted 
     because the default definition would be ill-formed: 

error: use of deleted function ‘Sample::Sample(const Sample&)’ 
SomeFunc(s1); 
     ^

che ti dà un testa a testa per aggiungere un costruttore di copia equivalente a:

 Sample(const Sample & other) 
    { 
     ptr = std::unique_ptr<int>(new int(*other.ptr)); 
    } 

Più probabilmente voler cambiare Sample s1 = 10; a Sample s1 (10); per evitare la copia lì. Del resto, potresti aver voluto che SomeFunc prendesse i suoi valori anche come riferimento. Menzionerò anche lo initializer lists vs assignments.

(Nota:. C'è in realtà un nome per il modello di una classe puntatore intelligente che copia chiamato clone_ptr, in modo da non dover scrivere anche che costruttore di copia Non è nella libreria standard C++, ma you'll find implementations around.)

Qualora i Sample "copie" condividere una dinamica ptr comune che viene eliminato solo dopo l'ultimo riferimento va via?

Più facile con puntatori intelligenti e nessun costruttore di copia necessario sul campione. Utilizzare un shared_ptr. Il comportamento predefinito di shared_ptr deve poter essere copiato con assegnazioni semplici.

class Sample 
{   
public: 
    shared_ptr<int> ptr; 
    Sample(int i) 
    { 
     ptr = make_shared<int>(i); 
    } 
    ~Sample() 
    { 
     cout << "destroyed"; 
    } 
    void PrintVal() 
    { 
     cout << "The value is " << *ptr; 
    } 
}; 

Morale della favola è che più si può lasciare che i comportamenti predefiniti fare il lavoro giusto per te ... e il codice si scrive meno ... meno potenziale avete per i bug. Quindi, mentre è bello sapere cosa fanno i costruttori di copie e quando vengono richiamati, può essere altrettanto importante sapere come scrivere non!

Nota che unique_ptr e shared_ptr provengono da C++ 11 e richiedono #include <memory>.

Problemi correlati