Dlaczego destruktor nie jest wywoływany w operatorze kasowania?

16

Próbowałem wezwać ::deleteklasę operator delete. Ale destruktor nie jest nazywany.

I zdefiniowano klasę MyClassktórego operator deletezostał przeciążony. Globalny operator deletejest również przeciążony. Przeciążonej operator deletez MyClasswezwie przeciążonej globalny 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;
}

Dane wyjściowe to:

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

Rzeczywisty:

Jest tylko jedno wywołanie destruktora przed wywołaniem przeciążony operator deleteod MyClass.

Spodziewany:

Istnieją dwa połączenia z destruktorem. Jeden przed wywołaniem przeciążony operator deleteod MyClass. Kolejny przed wezwaniem globalnego operator delete.

wykreślić
źródło
6
MyClass::operator new()powinien przydzielić pamięć surową (przynajmniej) sizebajtów. Nie powinna próbować całkowicie zbudować instancji MyClass. Konstruktor MyClassjest wykonywany po MyClass::operator new(). Następnie deletewyrażenie w main()wywołuje destruktor i zwalnia pamięć (bez ponownego wywoływania destruktora). ::delete pWyrażenie nie posiada informacji na temat rodzaju obiektów pwskazuje na, ponieważ pjest void *, więc nie może wywołać destruktor.
Peter
Powiązane: stackoverflow.com/a/8918942/845092
Mooing Duck
2
Odpowiedzi już udzielone są poprawne, ale zastanawiam się: dlaczego próbujesz zastąpić nowe i usunąć? Typowym przypadkiem użycia jest wdrożenie niestandardowego zarządzania pamięcią (GC, pamięć, która nie pochodzi z domyślnego malloc () itp.). Może używasz niewłaściwego narzędzia do tego, co próbujesz osiągnąć.
noamtm
2
::delete p;powoduje niezdefiniowane zachowanie, ponieważ typ *pnie jest taki sam jak typ usuwanego obiektu (ani klasa bazowa z wirtualnym destruktorem)
MM
@MM Główne kompilatory najwyżej go ostrzegają, więc nie zdawałem sobie sprawy, że void*ponieważ operand jest nawet wyraźnie źle sformułowany. [expr.delete] / 1 : " Operand będzie wskazywał na typ obiektu lub typ klasy. [...] Oznacza to, że obiektu nie można usunąć za pomocą wskaźnika typu void, ponieważ void nie jest typem obiektu. * „@OP Poprawiłem swoją odpowiedź.
orzech

Odpowiedzi:

17

Nadużywacie operator newi operator delete. Operatory te to funkcje alokacji i dezalokacji. Nie ponoszą odpowiedzialności za konstruowanie lub niszczenie obiektów. Są one odpowiedzialne tylko za zapewnienie pamięci, w której obiekt zostanie umieszczony.

Globalne wersje tych funkcji to ::operator newi ::operator delete. ::newi ::deletesą nowymi / delete-wyrażeniami, podobnie jak new/ delete, różniącymi się od nich ::newi ::deleteomijają specyficzne dla klasy operator new/ operator deleteprzeciążenia.

Nowe / delete-wyrażenia konstruują / niszczą i przydzielają / zwalniają (przez wywołanie odpowiedniego operator newlub operator deleteprzed budową lub po zniszczeniu).

Ponieważ przeciążenie jest odpowiedzialne tylko za część alokacji / dezalokacji, powinno ono zadzwonić, ::operator newa ::operator deletezamiast ::newi ::delete.

deleteW delete myClass;jest odpowiedzialny za wywołanie destruktora.

::delete p;nie wywołuje destruktora, ponieważ pma typ void*i dlatego wyrażenie nie może wiedzieć, który wywołać destruktor. Prawdopodobnie zadzwoni ::operator deletedo zastąpionego w celu zwolnienia pamięci, chociaż użycie void*argumentu as do wyrażenia kasowania jest źle sformułowane (patrz edycja poniżej).

::new MyClass();wywołuje zastąpiony w ::operator newcelu przydzielenia pamięci i konstruuje w nim obiekt. Wskaźnik do tego obiektu jest zwracany jako void*do nowego wyrażenia w MyClass* myClass = new MyClass();, które następnie skonstruuje inny obiekt w tej pamięci, kończąc żywotność poprzedniego obiektu bez wywoływania jego destruktora.


Edytować:

Dzięki komentarzowi @ MM do pytania zdałem sobie sprawę, że void*operand jako ::deletejest właściwie źle sformułowany. ( [expr.delete] / 1 ) Jednak wydaje się, że główne kompilatory zdecydowały się tylko ostrzec o tym, a nie o błędzie. Zanim to było robione źle sformułowane, używając ::deletena void*już wcześniej niezdefiniowanych zachowań, patrz na to pytanie .

Dlatego twój program jest źle sformułowany i nie masz żadnej gwarancji, że kod faktycznie zrobi to, co opisałem powyżej, jeśli nadal będzie mógł się skompilować.


Jak wskazał @SanderDeDycker poniżej jego odpowiedzi, masz również niezdefiniowane zachowanie, ponieważ konstruując w pamięci inny obiekt, który już zawiera MyClassobiekt, bez uprzedniego wywołania destruktora tego obiektu, naruszasz [basic.life] / 5, co zabrania tego, jeśli program zależy od skutków ubocznych niszczyciela. W takim przypadku printfwypowiedź w destruktorze wywołuje taki efekt uboczny.

orzech włoski
źródło
Niewłaściwe użycie ma na celu sprawdzenie działania tych operatorów. Jednak dzięki za odpowiedź. Wydaje się, że to jedyna odpowiedź, która rozwiązuje mój problem.
expinc
13

Przeciążenia specyficzne dla klasy są wykonywane nieprawidłowo. Widać to w wynikach: konstruktor jest wywoływany dwukrotnie!

W przypadku klasy operator newzadzwoń bezpośrednio do operatora globalnego:

return ::operator new(size);

Podobnie w przypadku klasy operator deletewykonaj:

::operator delete(p);

Więcej informacji znajduje się na operator newstronie odniesienia.

Sander De Dycker
źródło
Wiem, że konstruktor jest wywoływany dwukrotnie przez wywołanie :: new in operator new i mam na myśli to. Moje pytanie brzmi: dlaczego destruktor nie jest wywoływany podczas wywoływania :: delete w operator delete?
expinc
1
@expinc: Celowo wywołanie konstruktora po raz drugi bez wywoływania destruktor pierwszy jest naprawdę zły pomysł. W przypadku nietrywialnych niszczycieli (takich jak twój) wchodzisz nawet w nieokreślone terytorium zachowania (jeśli zależysz od efektów ubocznych niszczyciela, które robisz) - ref. [basic.life] §5 . Nie rób tego
Sander De Dycker,
1

Zobacz odniesienie do CPP :

operator delete, operator delete[]

Cofa pamięć przydzieloną wcześniej przez dopasowanie operator new. Te funkcje dezalokacji są wywoływane przez wyrażenia usuwania i wyrażenia nowe w celu zwolnienia pamięci po zniszczeniu (lub nieudaniu) obiektów z dynamicznym czasem przechowywania. Można je również wywoływać przy użyciu składni regularnych wywołań funkcji.

Usuń (i nowe) są odpowiedzialne tylko za część „zarządzanie pamięcią”.

Jest więc jasne i oczekiwane, że destruktor jest wywoływany tylko raz - w celu wyczyszczenia instancji obiektu. Gdyby został wywołany dwukrotnie, każdy niszczyciel musiałby sprawdzić, czy został już wywołany.

Mario The Spoon
źródło
1
Operator usuwania powinien nadal domyślnie wywoływać destruktor, jak widać z jego własnych dzienników pokazujących destruktor po usunięciu instancji. Problem polega na tym, że jego zastąpienie usuwania klasy wywołuje :: delete, co prowadzi do jego globalnego zastąpienia usuwania. To globalne zastąpienie usuwa tylko pamięć, więc nie wywoła ponownie destruktora.
Pickle Rick
Odniesienie wyraźnie stwierdza, że ​​usunięcie nazywa się PO dekonstrukcji obiektu
Mario The Spoon,
Tak, globalne usuwanie jest wywoływane po usunięciu klasy. Są tutaj dwa przesłonięcia.
Pickle Rick
2
@PickleRick - chociaż prawdą jest, że wyrażenie usuwania powinno wywoływać destruktor (zakładając, że dostarczono wskaźnik do typu z destruktorem) lub zestawem destruktorów (forma tablicowa), operator delete()funkcja nie jest tym samym, co wyrażenie usuwania. Destruktor jest wywoływany przed wywołaniem operator delete()funkcji.
Peter
1
Pomogłoby to dodanie nagłówka do qoute. Obecnie nie jest jasne, do czego się odnosi. „Dellocates storage ...” - kto zwalnia pamięć?
idclev 463035818,