Nano Hash - криптовалюты, майнинг, программирование

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

Я попытался вызвать ::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.

21.10.2019

  • MyClass::operator new() должен выделять необработанную память (как минимум) size байт. Не следует пытаться полностью создать экземпляр MyClass. Конструктор MyClass выполняется после MyClass::operator new(). Затем выражение delete в main() вызывает деструктор и освобождает память (без повторного вызова деструктора). Выражение ::delete p не содержит информации о типе объекта, на который указывает p, поскольку p является void *, поэтому не может вызвать деструктор. 21.10.2019
  • Связано: stackoverflow.com/a/8918942/845092 21.10.2019
  • Ответы, уже данные вам, верны, но мне интересно: почему вы пытаетесь переопределить новые и удалить? Типичным вариантом использования является реализация пользовательского управления памятью (GC, память, которая не поступает из malloc() по умолчанию и т. д.). Возможно, вы используете неправильный инструмент для того, чего пытаетесь достичь. 22.10.2019
  • ::delete p; вызывает неопределенное поведение, поскольку тип *p не совпадает с типом удаляемого объекта (или базовым классом с виртуальным деструктором) 30.10.2019
  • @MM Основные компиляторы в лучшем случае только предупреждают об этом, поэтому я не осознавал, что void* в качестве операнда даже явно неправильно сформирован. [expr.delete]/1: Операнд должен иметь тип указателя на объект или тип класса. [...] Это означает, что объект нельзя удалить с помощью указателя типа void, потому что void не является типом объекта.* @OP Я изменил свой ответ. 30.10.2019
  • Вот работающая версия: onlinegdb.com/Hy6jr9IqS 30.10.2019
  • @uneven_mark, по-видимому, void * является типом указателя объекта, но не указателем на тип объекта, согласно [basic.compound]/3. Ясно, как грязь... 31.10.2019
  • @MM Стандарт также смешивает указатель написания на тип объекта и указатель на тип объекта. Я надеюсь, что они имеют в виду то же самое... 31.10.2019

Ответы:


1

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

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

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

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

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

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

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


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

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

Следовательно, ваша программа имеет неправильный формат, и у вас нет никакой гарантии, что код действительно делает то, что я описал выше, если его все же удалось скомпилировать.


Как указал @SanderDeDycker ниже своего ответа, у вас также есть неопределенное поведение, потому что, создавая в памяти другой объект, который уже содержит объект MyClass, без предварительного вызова деструктора этого объекта, вы нарушаете [basic.life]/5, который запрещает делать это, если программа зависит от побочных эффектов деструктора. В этом случае оператор printf в деструкторе имеет такой побочный эффект.

21.10.2019
  • Неправильное использование предназначено для проверки работы этих операторов. Тем не менее, спасибо за ваш ответ. Кажется, это единственный ответ, который решает мою проблему. 25.10.2019

  • 2

    Ваши специфичные для класса перегрузки выполняются неправильно. Это видно из вашего вывода: конструктор вызывается дважды!

    В специфичном для класса operator new вызовите глобальный оператор напрямую:

    return ::operator new(size);
    

    Точно так же в специфичном для класса operator delete выполните:

    ::operator delete(p);
    

    Дополнительные сведения см. на справочной странице operator new.

    21.10.2019
  • Я знаю, что конструктор вызывается дважды, вызывая ::new в операторе new, и я имею в виду это. Мой вопрос: почему деструктор не вызывается при вызове ::delete в операторе удаления? 25.10.2019
  • @expinc: преднамеренный вызов конструктора во второй раз без вызова деструктора first - действительно плохая идея. Для нетривиальных деструкторов (таких как ваш) вы даже рискуете попасть на территорию неопределенного поведения (если вы зависите от побочных эффектов деструктора, которые вы делаете) - ref. [basic.life] §5. Не делай этого. 25.10.2019

  • 3

    См. Справочник по CPP:

    operator delete, operator delete[]

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

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

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

    21.10.2019
  • Оператор удаления по-прежнему должен неявно вызывать деструктор, как вы можете видеть из его собственных журналов, показывающих деструктор после удаления экземпляра. Проблема здесь в том, что его переопределение удаления класса вызывает ::delete, что приводит к его глобальному переопределению удаления. Это глобальное переопределение удаления просто освобождает память, поэтому он не будет повторно вызывать деструктор. 21.10.2019
  • В ссылке четко указано, что удаление вызывается ПОСЛЕ деконструкции объекта. 21.10.2019
  • Да, глобальное удаление вызывается после удаления класса. Здесь есть два переопределения. 21.10.2019
  • @PickleRick - хотя верно то, что выражение удаления должно вызывать деструктор (при условии, что указан указатель на тип с деструктором) или набор деструкторов (форма массива), функция operator delete() не является то же самое, что и выражение удаления. Деструктор вызывается до вызова функции operator delete(). 21.10.2019
  • Я знаю об этом, может быть, здесь есть недоразумение? Я только говорю, что причина, по которой он не был уничтожен дважды (о чем он спрашивает), заключается в том, что удаление класса не совпадает с глобальным удалением, которое в его случае просто освобождает память. 21.10.2019
  • Было бы полезно, если бы вы добавили заголовок в qoute. В настоящее время неясно, о чем идет речь. Деллоцирует память... - кто освобождает память? 21.10.2019
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..