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

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.

expinc
источник
6
MyClass::operator new()должен выделять необработанную память, из (как минимум) sizeбайтов. Он не должен пытаться полностью создать экземпляр MyClass. Конструктор MyClassвыполняется после MyClass::operator new(). Затем deleteвыражение in main()вызывает деструктор и освобождает память (без повторного вызова деструктора). ::delete pВыражение не имеет никакой информации о типе объекта pточек на, так как pэто void *, так что не может вызывать деструктор.
Питер
Связанный: stackoverflow.com/a/8918942/845092
Mooing Duck
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