2009-07-10 7 views
16
class Widget 
{ 
    public: 
     Widget() { 
      cout<<"~Widget()"<<endl; 
     } 
     ~Widget() { 
      cout<<"~Widget()"<<endl; 
     } 

    void* operator new(size_t sz) throw(bad_alloc) { 
     cout<<"operator new"<<endl; 
     throw bad_alloc(); 
    } 

    void operator delete(void *v) { 
     cout<<"operator delete"<<endl; 
    } 

}; 

int main() 
{ 
    Widget* w = 0; 
    try { 
     w = new Widget(); 
    } 
    catch(bad_alloc) { 
     cout<<"Out of Memory"<<endl; 
    } 

    delete w; 
    getch(); 
    return 1; 
} 

In questo codice, delete w non chiama il sovraccarico delete operatore quando il distruttore è lì. Se il distruttore viene omesso, viene chiamato l'overload delete. Perché è così?eliminare un puntatore NULL non chiamare sovraccaricato elimina quando distruttore è scritto

uscita quando ~ Widget() è scritto

operatore nuovo
Memoria

uscita quando ~ Widget() non è scritto

operatore new
Memoria
operatore eliminare

+0

Questa è una domanda stranamente affascinante! Ho appena guardato in Effective C++ di Scott Meyer (sfortunatamente ho solo 2 ° Ed) e non ho visto nulla al riguardo. –

risposta

21

mi ricordo qualcosa di simile su operatore eliminare qualche tempo fa in comp .lang.C++. moderato. Non riesco a trovare ora, ma la risposta dichiarato qualcosa di simile ..

Purtroppo, le specifiche del linguaggio non è sufficientemente chiaro se il controllo dovrebbe andare in 'operatore delete' sovraccaricato quando l'eliminazione -espressione è invocata sul puntatore nullo del corrispondente tipo , anche se lo standard fa dire che delete-expression su null-pointer è un no-op.

E James Kanze specificamente detto:

è ancora il responisiblity di operatore delete (o eliminare []) per controllo; lo standard non garantisce che non verrà assegnato un puntatore nullo; lo standard richiede che sia un no-op se viene fornito un puntatore nullo. O che a l'implementazione è autorizzata a chiamare lo . Secondo l'ultima bozza, "Il valore del primo argomento fornito a una funzione deallocazione può essere un valore null pointer, in tal caso, e se la funzione deallocazione è quello fornito nella libreria standard, la chiamata ha nessun effetto." Io non sono molto sicuro di cosa le implicazioni di che "è quello fornito nella libreria standard" sono destinate ad essere --- preso alla lettera, poiché la sua funzione non è quella fornita dalla libreria standard, la frase non sembrerebbe applicarsi. Ma in qualche modo, che non ha senso

mi ricordo questo becoz ho avuto un prob simile qualche tempo indietro e aveva conservato la risposta in un file .txt.

UPDATE-1:

Oh ho trovato here. Leggi anche questo link defect report. Quindi, la risposta è Non specificato. Capitolo 5.3.5/7.

+0

+1 suona come il più vicino ad una risposta come avremo ... anche se attendo ulteriori sviluppi con il fiato sospeso. –

+0

Se deve essere un no-op, il compilatore non può eseguire alcun codice utente, dal momento che quel codice può causare effetti collaterali (ad esempio fare un print thingie) – xtofl

3

Non ho una buona risposta, ma ho semplificato un po 'la questione. Il codice seguente rimuove il nuovo e il trattamento delle eccezioni esercente:

#include <iostream> 
using namespace std; 

class Widget { 

    public: 
    Widget() { 
     cout<<"Widget()"<<endl; 
    } 
    ~Widget() { 
     cout<<"~Widget()"<<endl; 
    } 

    void operator delete(void *v) { 
     cout << "operator delete" << endl; 
    } 
}; 

int main() { 
    Widget* w = 0; 
    cout << "calling delete" << endl; 
    delete w; 
} 

Questo presenta ancora lo stesso comportamento e des così sia VC++ g ++.

Ovviamente, l'eliminazione di un puntatore NULL è un no-op, quindi il compilatore non deve chiamare l'operatore delete. Se uno effettivamente assegna un oggetto:

Widget* w = new Widget; 

quindi le cose funzionano come previsto.

-2

Si stava tentando di eliminare un puntatore NULL. Quindi, il distruttore non è stato chiamato.

class Widget 
{ 
public:   
    Widget() 
    {    
     cout<<"Widget()"<<endl;   
    }  

    ~Widget() 
    {   
     cout<<"~Widget()"<<endl;  
    }  

    void* operator new(size_t sz) throw(bad_alloc) 
    {  
     cout<<"operator new"<<endl; 
     return malloc(sizeof(Widget)); 
     //throw bad_alloc();  
    } 

    void operator delete(void *v) 
    {    
     cout<<"operator delete"<<endl; 
    } 
}; 

int main() 
{ 

    Widget* w = NULL; 
    try 
    { 
     w = new Widget(); 
     //throw bad_alloc(); 
    } 
    catch(bad_alloc) 
    {   
     cout<<"Out of Memory"<<endl; 
    } 
    delete w; 
} 

uscita:

operatore nuovo
Widget()
~ Widget()
operatore delete

+0

Sì, ma la domanda è: perché la sua eliminazione personalizzata viene chiamata quando delete viene chiamato con NULL? (Ma solo se la classe non ha distruttore?) –

+1

La domanda non è il motivo per cui il distruttore non viene chiamato. La domanda è piuttosto che quando non fornisco un distruttore (nel qual caso il compilatore ne crea uno per me), perché viene chiamato l'overloading sovraccarico ?? –

4

Il motivo è che se si dispone di un distruttore, la chiamata all'operatore di eliminazione viene eseguita all'interno del distruttore di eliminazione scalare, che in VC contiene la chiamata sia al distruttore che all'operatore di cancellazione. Il compilatore fornisce il codice che controlla se stai tentando di eliminare un puntatore NULL. L'eliminazione di tale puntatore è legale, ovviamente, ma il distruttore di tale oggetto non deve essere invocato, poiché potrebbe contenere l'utilizzo di variabili membro. Per questo viene evitata la chiamata all'eliminazione scalare del distruttore e, di conseguenza, viene evitata anche la chiamata all'operatore di cancellazione.

Quando non c'è un distruttore, il compilatore chiama direttamente l'operatore di cancellazione, senza generare il distruttore di cancellazione scalare. Pertanto, in tali casi l'operatore di cancellazione viene invocato dopo tutto.

+3

Questo non è del tutto corretto - 'delete' ovviamente non può essere fatto dal distruttore, perché un distruttore verrebbe chiamato per gli oggetti di quella classe con l'archiviazione statica e automatica, e chiamare 'delete' su quelli è ovviamente indesiderabile. Quello che succede invece è che VC++ genera una funzione _extra_ chiamata "scalare che elimina il distruttore", che prima chiama il distruttore vero e proprio (che è ancora generato come un'altra funzione separata), quindi esegue "cancella questo". E poi chiama quella cancellazione scalare del distruttore ovunque tu faccia 'cancella' su oggetti di quel tipo. –

+0

Grazie per la correzione, Pavel. Risolta la mia risposta di conseguenza. – eran

+0

Penso che questa sia la risposta corretta in quanto è supportata dal fatto che se il dtor viene rimosso dal Widget, ma viene aggiunto un oggetto con un dtor, si otterrà lo stesso comportamento. –

-1

Il distruttore dell'oggetto viene chiamato prima dell'operatore di cancellazione. Quindi la mia ipotesi è che si tenta di chiamare il distruttore, si rende conto che il puntatore è NULL pertanto

  1. non chiama distruttore che ha bisogno di un'istanza
  2. interrompe l'operazione deleteing lì (tipo di ottimizzazione IMHO velocità) .

Come diceva Neil, se w contiene un Widget, dovrebbe funzionare.

+0

Se non chiama il distruttore, lo stesso comportamento dovrebbe essere mostrato quando non scrivo un distruttore (nel qual caso il compilatore dovrebbe crearne uno per me). Il punto principale è il motivo per cui il comportamento è diverso nei 2 casi –

+0

Perché quando il distruttore non viene specificato l'operatore delete viene invocato direttamente (comportamento predefinito), mentre quando viene specificato, c'è questo "pre-controllo" dell'istanza Sto affermando qui. Qui sto spiegando il caso _with_ il distruttore, il confronto era implicito - forse non abbastanza chiaramente, però. –

9

Prima di tutto, questo può davvero essere semplificato fino a delete (Widget*)0 - tutto il resto nel tuo main() non è necessario ripubblicarlo.

Si tratta di un artefatto di generazione di codice che si verifica perché 1) definito dall'utente operator delete deve essere in grado di gestire i valori NULL e 2) il compilatore tenta di generare il codice più ottimale possibile.

Consideriamo innanzitutto il caso in cui non è coinvolto alcun distruttore definito dall'utente. Se questo è il caso, non c'è alcun codice da eseguire sull'istanza, ad eccezione di operator delete. Non ha senso verificare il valore null prima di trasferire il controllo a operator delete, perché quest'ultimo dovrebbe comunque effettuare un controllo; e così il compilatore genera semplicemente una chiamata incondizionata di operator delete (e vedi quest'ultimo stampare un messaggio).

Ora è stato definito il secondo caso: distruttore. Ciò significa che l'istruzione delete si espande effettivamente in due chiamate: destructor e operator delete. Ma il distruttore non può essere tranquillamente chiamato su un puntatore nullo, perché potrebbe provare ad accedere ai campi di classe (il compilatore potrebbe capire che il proprio distruttore non lo fa veramente e quindi è sicuro chiamare con il numero this, ma sembra che non lo facciano non importa in pratica). Quindi inserisce un controllo null lì prima della chiamata del distruttore. E una volta che l'assegno è già lì, potrebbe anche essere utile saltare la chiamata allo operator delete, dopotutto è necessario essere un no-op in ogni caso, e risparmierà un ulteriore controllo senza significato per null all'interno dello stesso operator delete nel caso in cui il il puntatore in realtà è nullo.

Per quanto posso vedere, nulla di ciò è in alcun modo garantito dalle specifiche ISO C++. È solo che entrambi i compilatori fanno lo stesso ottimizzazione qui.

+2

Bella spiegazione! Imho, c'è anche un terzo caso: distruttore virtuale definito. Questo non può essere trovato in fase di esecuzione se il puntatore è NULL. Un altro motivo per cui l'eliminazione dei puntatori NULL non può chiamare il codice del distruttore ... – xtofl

3

Vorrei lasciare un commento, invece di rispondere, non aveva privilegi sufficienti per essere un nuovo membro.

Un'eccezione viene sollevata durante la creazione dell'oggetto. Il distruttore non viene chiamato, poiché l'oggetto stesso non viene creato.

Che si può anche osservare, come i messaggi dal costruttore & distruttore non vengono visualizzati.

Tuttavia, l'eliminazione viene chiamata quando il distruttore non è definito. Se si pensa nel directon che quando destrcutor non è definito, il compilatore C++ lo considera come qualsiasi altro operatore, il compilatore fornisce di default un distruttore quando non è definito.

+1

Questo è decisamente semplice. –

+0

E anche un buon punto! – xtofl

Problemi correlati