Perché il distruttore non chiamato nell'operatore viene eliminato?


16

Ho provato a convocare ::deleteuna lezione in operator deleteesso. Ma il distruttore non viene chiamato.

Ho definito una classe il MyClasscui operator deletesovraccarico è stato. Anche il globale operator deleteè sovraccarico. Il sovraccarico operator deletedi MyClasschiamerà il sovraccarico globale operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

L'output è:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Effettivo:

C'è solo una chiamata al distruttore prima di chiamare il sovraccarico operator deletedi MyClass.

Previsto:

Ci sono due chiamate al distruttore. Uno prima di chiamare il sovraccarico operator deletedi MyClass. Un altro prima di chiamare il globale operator delete.


6
MyClass::operator new()dovrebbe allocare memoria grezza, di (almeno) sizebyte. Non dovrebbe tentare di costruire completamente un'istanza di MyClass. Il costruttore di MyClassviene eseguito dopo MyClass::operator new(). Quindi, l' deleteespressione in main()chiama il distruttore e rilascia la memoria (senza chiamare nuovamente il distruttore). L' ::delete pespressione non ha informazioni sul tipo di ppunti oggetto in, poiché pè a void *, quindi non può invocare il distruttore.
Peter,


2
Le risposte che ti sono già state fornite sono corrette, ma mi chiedo: perché stai cercando di ignorare nuove ed eliminare? Il tipico caso d'uso è l'implementazione della gestione della memoria personalizzata (GC, memoria che non proviene dal malloc predefinito (), ecc.). Forse stai usando lo strumento sbagliato per quello che stai cercando di ottenere.
Noamtm,

2
::delete p;causa un comportamento indefinito poiché il tipo di *pnon è uguale al tipo di oggetto da eliminare (né una classe base con distruttore virtuale)
MM

@MM I compilatori principali lo avvertono solo al massimo, quindi non mi sono reso conto che void*l'operando è persino esplicitamente mal formato. [expr.delete] / 1 : " L'operando deve essere di puntatore al tipo di oggetto o di tipo di classe. [...] Ciò implica che un oggetto non può essere cancellato usando un puntatore di tipo void perché void non è un tipo di oggetto. * "@OP Ho modificato la mia risposta.
noce,

Risposte:


17

Stai abusando operator newe operator delete. Questi operatori sono funzioni di allocazione e deallocazione. Non sono responsabili della costruzione o della distruzione di oggetti. Sono responsabili solo di fornire la memoria in cui verrà posizionato l'oggetto.

Le versioni globali di queste funzioni sono ::operator newe ::operator delete. ::newe ::deletesono nuove espressioni / delete, in quanto sono new/ delete, diverse da quelle, in questo ::newe ::deletebypasseranno specifiche operator new/ operator deletesovraccarichi di classe .

Le nuove espressioni / eliminano-costruiscono / distruggono e allocare / deallocare (chiamando l'appropriato operator newo operator deleteprima della costruzione o dopo la distruzione).

Poiché il tuo sovraccarico è responsabile solo della parte allocazione / deallocazione, dovrebbe chiamare ::operator newe ::operator deleteinvece di ::newe ::delete.

L' deleteIn delete myClass;è responsabile della chiamata del distruttore.

::delete p;non chiama il distruttore perché pha tipo void*e quindi l'espressione non può sapere quale distruttore chiamare. Probabilmente chiamerà il tuo rimpiazzato ::operator deleteper deallocare la memoria, sebbene l'uso di un void*operando as per un'espressione di eliminazione non sia corretto (vedi modifica sotto).

::new MyClass();chiama il sostituito ::operator newper allocare memoria e costruisce un oggetto al suo interno. Il puntatore a questo oggetto viene restituito void*sulla nuova espressione in MyClass* myClass = new MyClass();, che quindi costruirà un altro oggetto in questa memoria, ponendo fine alla durata dell'oggetto precedente senza chiamare il suo distruttore.


Modificare:

Grazie al commento di @ MM sulla domanda, mi sono reso conto che un void*as operando ::deleteè in realtà mal formato. ( [expr.delete] / 1 ) Tuttavia, i principali compilatori sembrano aver deciso di mettere in guardia solo su questo, non sull'errore. Prima che fosse reso mal formato, usando ::deleteun void*comportamento già indefinito, vedi questa domanda .

Pertanto, il tuo programma è mal formato e non hai alcuna garanzia che il codice faccia effettivamente quello che ho descritto sopra se è ancora riuscito a compilare.


Come sottolineato da @SanderDeDycker sotto la sua risposta, hai anche un comportamento indefinito perché costruendo un altro oggetto nella memoria che contiene già un MyClassoggetto senza chiamare prima il distruttore di quell'oggetto, stai violando [basic.life] / 5 che proibisce di farlo se il il programma dipende dagli effetti collaterali del distruttore. In questo caso la printfdichiarazione nel distruttore ha un tale effetto collaterale.


L'uso improprio ha lo scopo di verificare come funziona questo operatore. Tuttavia, grazie per la tua risposta. Sembra l'unica risposta che risolva il mio problema.
scadenza

13

I tuoi sovraccarichi specifici per classe vengono eseguiti in modo errato. Questo può essere visto nel tuo output: il costruttore viene chiamato due volte!

Nello specifico della classe operator new, chiamare direttamente l'operatore globale:

return ::operator new(size);

Allo stesso modo, nello specifico della classe operator delete, fare:

::operator delete(p);

Fare operator newriferimento alla pagina di riferimento per maggiori dettagli.


So che il costruttore viene chiamato due volte chiamando :: new in operator new e intendo. La mia domanda è: perché il distruttore non viene chiamato quando si chiama :: delete in operator delete?
scadenza

1
@expinc: chiamare intenzionalmente il costruttore una seconda volta senza chiamare prima il distruttore è una pessima idea. Per i distruttori non banali (come i tuoi), ti stai persino avventurando in un territorio di comportamento indefinito (se dipendi dagli effetti collaterali del distruttore, cosa che fai) - rif. [basic.life] §5 . Non farlo
Sander De Dycker,

1

Vedi riferimento CPP :

operator delete, operator delete[]

Deallocates archiviazione precedentemente allocata da una corrispondenza operator new. Queste funzioni di deallocazione sono chiamate da delete-expressions e da new-expressions per deallocare la memoria dopo aver distrutto (o non è riuscito a costruire) oggetti con durata dinamica della memoria. Possono anche essere chiamati utilizzando la normale sintassi della chiamata di funzione.

Elimina (e nuovo) sono responsabili solo della parte "gestione della memoria".

Quindi è chiaro e ci si aspetta che il distruttore venga chiamato solo una volta - per ripulire l'istanza dell'oggetto. Se fosse chiamato due volte, ogni distruttore dovrebbe verificare se fosse già stato chiamato.


1
L'operatore di eliminazione dovrebbe comunque chiamare implicitamente il distruttore, come puoi vedere dai suoi log che mostrano il distruttore dopo aver eliminato l'istanza. Il problema qui è che il suo override di cancellazione di classe sta chiamando :: delete che porta al suo override di eliminazione globale. L'override globale dell'eliminazione libera semplicemente la memoria in modo da non provocare nuovamente il distruttore.
Pickle Rick,

Il riferimento afferma chiaramente che l'eliminazione si chiama DOPO la decostruzione dell'oggetto
Mario The Spoon,

Sì, l'eliminazione globale viene chiamata dopo l'eliminazione della classe. Ci sono due sostituzioni qui.
Pickle Rick,

2
@PickleRick - mentre è vero che un'espressione di eliminazione dovrebbe chiamare un distruttore (supponendo che abbia fornito un puntatore a un tipo con un distruttore) o un insieme di distruttori (modulo array), una operator delete()funzione non è la stessa cosa di un'espressione di eliminazione. Il distruttore viene chiamato prima che venga chiamata la operator delete()funzione.
Peter,

1
Sarebbe utile se si aggiungesse un'intestazione a qoute. Attualmente non è chiaro a cosa si riferisca. "Dellocates storage ..." - chi dealloca lo storage?
idclev 463035818,
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.