Почему деструктор не вызывается в операторе удаления?


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выражение in main()вызывает деструктор и освобождает память (без повторного вызова деструктора). ::delete pВыражение не имеет никакой информации о типе объекта pточек на, так как pэто void *, так что не может вызывать деструктор.
— Питер


2
Ответы, которые уже даны вам, верны, но мне интересно: почему вы пытаетесь переопределить новое и удалить? Типичный вариант использования - реализация пользовательского управления памятью (GC, память, которая не берется из malloc () по умолчанию и т. Д.). Может быть, вы используете неправильный инструмент для того, что вы пытаетесь достичь.
— Noamtm

2
::delete p;вызывает неопределенное поведение, так как тип *pне совпадает с типом удаляемого объекта (ни базовый класс с виртуальным деструктором)
— MM

@MM Основные компиляторы только предупреждают об этом, так что я не осознавал, что void*операнд даже явно плохо сформирован. [expr.delete] / 1 : " Операнд должен иметь указатель на тип объекта или тип класса. [...] Это означает, что объект нельзя удалить с помощью указателя типа void, поскольку void не является типом объекта. * "@OP Я исправил свой ответ.
— орех

Ответы:


17

Вы злоупотребляете operator newи operator delete. Эти операторы являются функциями выделения и освобождения. Они не несут ответственности за строительство или разрушение объектов. Они отвечают только за предоставление памяти, в которую будет помещен объект.

Глобальные версии этих функций ::operator newи ::operator delete. ::newи ::deleteновые / delete-выражения, как есть new/ delete, отличаются от тех, что ::newи ::deleteбудут обходить специфичные для класса operator new/ operator deleteперегрузки.

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

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

deleteВ delete myClass;отвечает за вызов деструктора.

::delete p;не вызывает деструктор, потому что pимеет тип, void*и поэтому выражение не может знать, какой деструктор вызвать. Вероятно, он будет вызывать вашу замену ::operator deleteдля освобождения памяти, хотя использование void*операнда as для выражения удаления некорректно (см. Правку ниже).

::new MyClass();вызывает вашу замену, ::operator newчтобы выделить память и создать в ней объект. Указатель на этот объект возвращается как void*новое выражение в MyClass* myClass = new MyClass();, которое затем создаст другой объект в этой памяти, заканчивая время жизни предыдущего объекта, не вызывая его деструктор.


Редактировать:

Благодаря комментарию @MM к этому вопросу, я понял, что void*операнд в as на ::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?
— 19

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

1

См. Ссылку CPP :

operator delete, operator delete[]

Распределяет память, ранее выделенную при сопоставлении operator new. Эти функции освобождения вызываются выражениями удаления и новыми выражениями для освобождения памяти после разрушения (или неудачи в построении) объектов с динамической длительностью хранения. Они также могут быть вызваны с использованием обычного синтаксиса вызова функций.

Delete (и new) отвечают только за часть «управление памятью».

Таким образом, ясно и ожидается, что деструктор вызывается только один раз - для очистки экземпляра объекта. Если бы он вызывался дважды, каждый деструктор должен проверить, был ли он уже вызван.


1
Оператор удаления должен все еще неявно вызывать деструктор, как вы можете видеть из его собственных журналов, показывающих деструктор после удаления экземпляра. Проблема здесь в том, что его переопределение удаления класса вызывает :: delete, что приводит к его глобальному переопределению удаления. Это глобальное переопределение удаления просто освобождает память, чтобы не вызывать деструктор.
— Рассол Рик

Ссылка ясно утверждает, что удаление называется ПОСЛЕ деконструкции объекта
— Марио Ложка

Да, глобальное удаление вызывается после удаления класса. Здесь есть два переопределения.
— Рассол Рик

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

1
Было бы полезно, если бы вы добавили заголовок в qoute. В настоящее время не ясно, к чему это относится. «Распределяет хранилище ...» - кто освобождает хранилище?
— idclev 463035818
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.