Чому деструктор не викликається оператором видалення?


16

Я намагався закликати ::deleteдо класу в operator deleteцьому. Але деструктор не викликається.

Я визначив клас MyClass, operator deleteперевантажений яким. Глобальний operator deleteтакож перевантажений. Перевантажений operator deleteз MyClassвикликатиме перевантажений глобальної 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;
}

Вихід:

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

Фактична:

Існує тільки один виклик деструктора перед викликом перевантажений operator deleteз MyClass.

Очікується:

Є два виклики деструктора. Один перед викликом перевантажений operator deleteз MyClass. Ще одне перед викликом глобального operator delete.


6
MyClass::operator new()має виділяти необроблену пам'ять (принаймні) sizeбайт. Він не повинен намагатися повністю сконструювати екземпляр MyClass. Конструктор MyClassвиконується після MyClass::operator new(). Потім deleteвираз у main()виклику деструктора і звільняє пам'ять (не викликаючи знову деструктора). ::delete pВираз не має ніякої інформації про тип об'єкта pточок на, так як pце void *, так що не може викликати деструктор.
Пітер


2
Відповіді, які вам уже дали, є правильними, але мені цікаво: чому ви намагаєтесь переосмислити нове та видалити? Типовим випадком використання є реалізація користувальницької керування пам'яттю (GC, пам'ять, яка не походить від malloc () за замовчуванням тощо). Можливо, ви використовуєте неправильний інструмент для того, чого намагаєтесь досягти.
noamtm

2
::delete p;викликає не визначену поведінку, оскільки тип *pне такий, як тип об'єкта, що видаляється (а також базовий клас з віртуальним деструктором)
ММ

@MM Основні компілятори на це попереджають лише не більше, тому я не усвідомлював, що void*операнд навіть явно не формується. [expr.delete] / 1 : " Операнд має бути вказівником на тип об'єкта або тип класу. [...] Це означає, що об'єкт не може бути видалений за допомогою покажчика типу void, тому що void не є типом об'єкта. * "@OP Я змінив свою відповідь.
волоський горіх

Відповіді:


17

Ви зловживаєте operator newі operator delete. Ці оператори виконують функції розподілу та розподілу. Вони не несуть відповідальності за будівництво або руйнування об'єктів. Вони відповідають лише за забезпечення пам'яті, в яку буде розміщений об'єкт.

Глобальні версії цих функцій є ::operator newі ::operator delete. ::newі ::deleteнове / ВИДАЛИТИ-вираз, як і new/ delete, що відрізняється від тих, що ::newі ::deleteбудуть обходити клас специфічних operator new/ operator deleteперевантаження.

Нові / delete-вирази будують / руйнують та розподіляють / ділокатують (викликаючи відповідні operator newабо operator deleteперед побудовою чи після знищення).

Оскільки ваше перевантаження відповідає лише за частину розподілу / розсилки, вона повинна викликати, ::operator newа ::operator deleteзамість ::newі ::delete.

deleteУ delete myClass;відповідає за виклик деструктора.

::delete p;не викликає деструктора, тому що pмає тип, void*і тому вираз не може знати, який деструктор викликати. Ймовірно, виклик вашої заміненої ::operator deleteмісцерозподілити пам'ять, хоча використання void*як операнда для виразу-виразу неправильно формується (див. Редагування нижче).

::new MyClass();викликає замінену, ::operator newщоб виділити пам'ять і побудує в ній об'єкт. Вказівник на цей об’єкт повертається як void*до нового виразу в MyClass* myClass = new MyClass();, який потім сконструює інший об’єкт у цій пам'яті, що закінчує термін дії попереднього об'єкта без виклику його деструктора.


Редагувати:

Завдяки коментарю @ MM до цього питання, я зрозумів, що void*як операнд ::deleteнасправді неправильно сформований. ( [expr.delete] / 1 ) Однак, схоже, основні компілятори вирішили лише попередити про це, а не помилку. Перед тим як це було зроблено погано сформованим, використовуючи ::deleteна void*було вже непередбачувана поведінка, см це питання .

Тому ваша програма неправильно сформована, і ви не маєте жодної гарантії, що код насправді виконує те, що я описав вище, якщо його все-таки вдалося скомпілювати.


Як вказує @SanderDeDycker нижче його відповіді, ви також маєте невизначене поведінку, оскільки, будуючи інший об’єкт у пам'яті, який вже містить MyClassоб'єкт, не викликаючи деструктора цього об’єкта, ви спочатку порушуєте [basic.life] / 5, що забороняє це робити, якщо Програма залежить від побічних ефектів деструктора. У цьому випадку printfзаява в деструкторі має такий побічний ефект.


Неправильне використання призначене для перевірки роботи цих операторів. Однак, дякую за вашу відповідь. Здається, єдина відповідь, яка вирішує мою проблему.
expinc

13

Ваші класичні перевантаження виконані неправильно. Це можна побачити у вашому висновку: конструктор викликається двічі!

У конкретному класі operator newзателефонуйте глобальному оператору безпосередньо:

return ::operator new(size);

Аналогічно, у конкретному класі operator delete:

::operator delete(p);

operator newДетальнішу інформацію див. На довідковій сторінці.


Я знаю, що конструктор викликається двічі, викликаючи :: new в операторі new, і я маю на увазі це. Моє запитання, чому деструктор не викликається при виклику :: delete в операторі delete?
expinc

1
@expinc: Навмисне виклик конструктора вдруге, не викликаючи спочатку деструктора, - це дуже погана ідея. Для нетривіальних деструкторів (як ваш) ви навіть ризикуєте на не визначену територію поведінки (якщо ви залежите від побічних ефектів деструктора, які ви робите) - замовте. [basic.life] §5 . Не робіть цього.
Сандер Де

1

Див. Довідку CPP :

operator delete, operator delete[]

Розподіляє попередньо виділене сховище operator new. Ці функції делокації викликаються видаленнями-виразами та новими виразами для розміщення пам’яті після руйнування (або неможливості побудови) об’єктів з динамічною тривалістю зберігання. Вони також можуть бути викликані за допомогою синтаксису звичайного виклику функції.

Видалення (та нові) відповідають лише за частину "управління пам'яттю".

Тож зрозуміло і очікується, що деструктор викликається лише один раз - для очищення екземпляра об'єкта. Як би його викликали двічі, кожен деструктор повинен був би перевірити, чи він уже викликався.


1
Оператор видалення все одно повинен неявно викликати деструктора, як видно з його власних журналів, що показують деструктор після видалення екземпляра. Проблема тут полягає в тому, що його переопределення видалення класу викликає :: delete, що призводить до його глобального переопрацювання видалення. Це глобальне перевизначення видалення просто звільняє пам’ять, тому не відкликає деструктора.
Рік


Так, глобальне видалення викликається після класу видалення. Тут є два відміни.
Пікл Рік

2
@PickleRick - хоча це правда, що вираз видалення повинен викликати деструктор (якщо припустити, що надано вказівник на тип із деструктором) або набір деструкторів (форма масиву), operator delete()функція не є тим самим, що вираз видалення. Деструктор викликається перед викликом operator delete()функції.
Пітер

1
Це допоможе, якщо ви додасте заголовок до qoute. Наразі не ясно, на що йдеться. "Розподіляє сховище ..." - хто розбирає сховище?
idclev 463035818
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.