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.


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


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.


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.


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.


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,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.