2010-10-27 16 views
6

Ho questo semplice codice C++, ma non so come usare il distruttore.Codice distruttore C++

class date { 

public: 
    int day; 
    date(int m) 
    { 
     day =m; 
    } 

    ~date(){ 
    cout << "I wish you have entered the year \n" << day;  
    } 
}; 


int main() 
{ 
    date ob2(12); 
    ob2.~date(); 
    cout << ob2.day; 
    return 0; 
} 

la domanda è, che cosa devo scrivere nel mio codice distruttore, che dopo aver chiamato il distruttore, allora sarà eliminare la variabile "giorno". ???

+0

Un sacco di risposte fanno riferimento "allo stack". Se non sei sicuro di quale sia lo stack, leggi questa pagina: http://web.archive.org/web/20071029040931/www.dirac.org/linux/gdb/02a-Memory_Layout_A_Stack.php –

risposta

1

Non chiamare il distruttore in modo esplicito.

Quando si crea l'oggetto in pila (come hai fatto) tutto ciò che serve è:

int main() 
{ 
    date ob2(12); 
    // ob2.day holds 12 
    return 0; // ob2's destructor will get called here, after which it's memory is freed 
} 

Quando si crea l'oggetto sul mucchio, è un po bisogno di delete la classe prima della sua distruttore si chiama e memoria viene liberata:

int main() 
{ 
    date* ob2 = new date(12); 
    // ob2->day holds 12 
    delete ob2; // ob2's destructor will get called here, after which it's memory is freed 
    return 0; // ob2 is invalid at this point. 
} 

(Non riuscendo a chiamare eliminare in questo ultimo esempio si tradurrà in perdita di memoria.)

Entrambi i modi hanno i loro vantaggi un d svantaggi. Il modo stack è MOLTO veloce con l'allocazione della memoria che l'oggetto occuperà e non è necessario eliminarlo esplicitamente, ma lo stack ha uno spazio limitato e non è possibile spostare quegli oggetti facilmente, velocemente e in modo pulito.

L'heap è il metodo migliore per farlo, ma quando si tratta di prestazioni, è lento allocare e devi gestire i puntatori. Ma hai molta più flessibilità con quello che fai con il tuo oggetto, è molto più veloce lavorare con i puntatori e hai più controllo sulla vita dell'oggetto.

+2

"L'heap è il modo preferito per farlo" - facendo * cosa *? Memorizzare oggetti? Perché sarebbe sbagliato: lo stack è assolutamente il modo preferito di farlo in C++. –

+0

Lavorare con le librerie e passare gli oggetti tra di loro è preferibile (anzi, vitale) per assegnarle all'heap. Certo, lo stack è veloce e sporco e in alcuni casi ha senso. Ma quando hai oggetti che vuoi usare, passare, spostare, copiare e contare il conteggio, allora lo stack non è posto per loro. Fino a quando C++ 0x sposta la semantica diventa comune che è ... – Marius

+1

scusate ma questo è sbagliato. Per il passaggio di argomenti, utilizzare riferimenti (const). Per i valori di ritorno - semplicemente copia. Non succederà nulla di brutto. Vedi per es. http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ Le librerie non dovrebbero mai richiedere argomenti allocati su heap. Questo sarebbe un ** molto ** cattivo design della biblioteca. E non c'è nulla di veloce e sporco nell'allocazione dello stack. –

9

Non è necessario chiamare il distruttore in modo esplicito. Questo viene fatto automaticamente alla fine del campo di applicazione dell'oggetto ob2, ad esempio alla fine della funzione main.

Inoltre, poiché l'oggetto dispone di archiviazione automatica, non è necessario eliminare la memoria. Anche questo è fatto automaticamente alla fine della funzione.

Calling distruttori manualmente è quasi mai necessario (solo in bassa a livello di codice biblioteca) e la cancellazione della memoria manualmente è necessaria solo (e solo un'operazione valida) quando la memoria è stato precedentemente acquisito utilizzando new (quando si lavora con i puntatori).

Poiché la gestione manuale della memoria è soggetta a perdite, il codice C++ moderno non tenta di utilizzare esplicitamente new e . Quando è veramente necessario utilizzare new, viene utilizzato un cosiddetto "puntatore intelligente" anziché un puntatore normale.

+0

Questo è tutto ok bene, ma credo che il poster abbia una domanda molto più fondamentale. – Marius

+0

Leggero chiarimento sul terzo paragrafo: chiamare esplicitamente i distruttori può essere accettabile (anche se brutto e spesso evitabile) quando si utilizza il posizionamento nuovo. – Sydius

+0

@Sydius: è per questo che ho scritto "solo nel codice libreria di basso livello". Non confondiamo ulteriormente l'OP lanciando note ancora più avanzate. –

14

Raramente è mai necessario chiamare il distruttore in modo esplicito. Invece, il distruttore viene chiamato quando un oggetto viene distrutto.

Per un oggetto come ob2 che è una variabile locale, è distrutta quando esce dall'ambito:

int main() 
{ 
    date ob2(12); 

} // ob2.~date() is called here, automatically! 

Se si alloca dinamicamente un oggetto utilizzando new, il suo distruttore è chiamato quando l'oggetto viene distrutto utilizzando delete. Se si dispone di un oggetto statico, il suo distruttore viene chiamato quando il programma termina (se il programma termina normalmente).

A meno che non si crea qualcosa usando dinamicamente new, non c'è bisogno di fare nulla esplicito per ripulirlo (così, ad esempio, quando ob2 è distrutto, tutte le sue variabili membro, tra cui day, vengono distrutti). Se crei qualcosa in modo dinamico, devi assicurarti che venga distrutto quando hai finito con esso; la procedura migliore consiste nell'utilizzare quello che viene chiamato "puntatore intelligente" per garantire che questa pulizia venga gestita automaticamente.

0

In questo caso il distruttore non deve eliminare la variabile giorno.

È sufficiente chiamare eliminare sulla memoria che è stato assegnato con nuovo.

Ecco come il codice sarà se si stesse utilizzando new e delete per innescare invocare il distruttore

class date { 

    public: int* day; 
    date(int m) { 
     day = new int; 
     *day = m; 
    } 

    ~date(){ 
     delete day; 
     cout << "now the destructor get's called explicitly"; 
    } 
}; 

int main() { 
    date *ob2 = new date(12); 
    delete ob2; 
    return 0; 
} 
+0

Oltre al fatto che il codice assegna memoria all'heap per memorizzare un intero, che altro c'è di sbagliato nello stile? –

+0

questo è quello che intendevo. Ma in aggiunta (come ho sottolineato nel mio post in basso), qualsiasi utilizzo di 'new' in congiunzione con i raw pointer è un po 'deprecato nel C++ moderno perché porta a così tanti problemi. Sicuramente per i principianti. –

+0

Il codice ha il problema di non avere un costruttore di copia e un operatore di assegnazione copia ben definiti, che è una trappola estremamente comune per i principianti. –

2

Solo in circostanze molto specifiche è necessario chiamare direttamente il distruttore. Per impostazione predefinita, il distruttore verrà chiamato dal sistema quando si crea una variabile di archiviazione automatica e non rientra nell'ambito o quando un oggetto assegnato dinamicamente con new viene distrutto con delete.

struct test { 
    test(int value) : value(value) {} 
    ~test() { std::cout << "~test: " << value << std::endl; } 
    int value; 
}; 
int main() 
{ 
    test t(1); 
    test *d = new t(2); 
    delete d;   // prints: ~test: 2 
}      // prints: ~test: 1 (t falls out of scope) 

Per completezza, (questo non deve essere usato in generale) la sintassi per chiamare il distruttore è simile a un metodo. Dopo il distruttore viene eseguito, la memoria non è più un oggetto di quel tipo (dovrebbe essere gestita come memoria grezzo):

int main() 
{ 
    test t(1); 
    t.~test();   // prints: ~test: 1 
         // after this instruction 't' is no longer a 'test' object 
    new (&t) test(2);  // recreate a new test object in place 
}      // test falls out of scope, prints: ~test: 2 

Nota: dopo aver chiamato il distruttore su t, quella posizione di memoria non è più un test, questo è il motivo della ricreazione dell'oggetto per mezzo del posizionamento nuovo.

+0

Il modo usuale di allocare memoria da utilizzare con il posizionamento nuovo è con malloc (o nuovo con un array). Questo è il primo esempio che vedo con la variabile stack. btw nel primo esempio, sarebbe test * d = nuovo test (2); –

+0

@VJo: Ho iniziato a scrivere un esempio con un buffer di memoria allocato nello stack, ma questo richiedeva l'utilizzo del posizionamento nuovo * prima * chiamando il distruttore (piccolo problema) e non mostrava il potenziale problema di più chiamate al distruttore. C'è anche un altro esempio * auto * allocato nel nuovo standard: con i requisiti minori posti sui membri di un sindacato, esso consente oggetti con costruttori/distruttori non banali. In tal caso, spetta all'utente chiamare esplicitamente il costruttore e il distruttore del membro attivo dell'unione. –

0

Anche se il distruttore sembra qualcosa che devi chiamare per eliminare o "distruggere" il tuo oggetto quando hai finito di usarlo, non dovresti usarlo in quel modo.

Il distruttore è qualcosa che viene chiamato automaticamente quando il tuo oggetto esce dall'ambito, cioè quando il computer lascia le "parentesi graffe" in cui hai istanziato l'oggetto. In questo caso, quando esci da main(). Non vuoi chiamarlo da solo.

0

Si può essere confusi da un comportamento non definito qui. Lo standard C++ non ha regole su ciò che accade se usi un oggetto dopo che il suo distruttore è stato eseguito, poiché è un comportamento indefinito, e quindi l'implementazione può fare qualsiasi cosa voglia. In genere, i progettisti di compilatori non fanno nulla di speciale per un comportamento indefinito, e quindi ciò che accade è un artefatto di quali altre decisioni di progettazione sono state prese. (Ciò può causare risultati davvero strani a volte.)

Pertanto, una volta eseguito il distruttore, il compilatore non ha alcun obbligo in merito a quell'oggetto. Se non ti riferisci di nuovo, non importa. Se fai riferimento ad esso, è un comportamento indefinito, e dal punto di vista dello Standard il comportamento non ha importanza, e poiché lo Standard non dice nulla, la maggior parte dei progettisti di compilatori non si preoccuperà di ciò che fa il programma.

In questo caso, la cosa più semplice da fare è lasciare l'oggetto inalterato, poiché non sta trattenendo risorse, e la sua memoria è stata allocata come parte dell'avvio della funzione e non verrà recuperata fino alla funzione uscite. Pertanto, il valore del membro dati rimarrà lo stesso.La cosa naturale da fare per il compilatore quando legge ob2.day è accedere alla posizione di memoria.

Come qualsiasi altro esempio di comportamento non definito, i risultati potrebbero cambiare in qualsiasi cambiamento di circostanze, ma in questo caso probabilmente non lo faranno. Sarebbe bello se i compilatori catturassero più casi di comportamento non definito e problemi di diagnostica, ma non è possibile per i compilatori rilevare tutti i comportamenti non definiti (alcuni si verificano in fase di runtime) e spesso non controllano comportamenti che non pensano probabile.

Problemi correlati