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


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.