Является ли вызов деструктором вручную всегда признаком плохого дизайна?

Я думал: они говорят, что если вы вызываете деструктор вручную – вы делаете что-то неправильно. Но всегда ли это так? Есть ли контр-примеры? Ситуации, когда необходимо называть это вручную или где трудно / невозможно / непрактично избегать этого?

    Вызов деструктора вручную требуется, если объект был создан с использованием перегруженной формы operator new() , за исключением случаев, когда используются перегрузки « std::nothrow »:

     T* t0 = new(std::nothrow) T(); delete t0; // OK: std::nothrow overload void* buffer = malloc(sizeof(T)); T* t1 = new(buffer) T(); t1->~T(); // required: delete t1 would be wrong free(buffer); 

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

    С C ++ 2011 существует еще одна причина использования явных вызовов деструктора: при использовании обобщенных объединений необходимо явно уничтожить текущий объект и создать новый объект с использованием места размещения new при изменении типа представляемого объекта. Кроме того, когда объединение уничтожено, необходимо явно вызвать деструктор текущего объекта, если он требует уничтожения.

    Все ответы описывают конкретные случаи, но есть общий ответ:

    Вы вызываете dtor явно каждый раз, когда вам нужно просто уничтожить объект (в смысле C ++), не освобождая память, в которой находится объект.

    Это обычно происходит во всей ситуации, когда распределение / освобождение памяти управляется независимо от создания / уничтожения объекта. В таких случаях строительство происходит через новое место на существующем fragmentе памяти, а разрушение происходит через явный вызов dtor.

    Вот пример:

     { char buffer[sizeof(MyClass)]; { MyClass* p = new(buffer)MyClass; p->dosomething(); p->~MyClass(); } { MyClass* p = new(buffer)MyClass; p->dosomething(); p->~MyClass(); } } 

    Другим примечательным примером является стандартный std::allocator при использовании std::vector : элементы строятся в vector во время push_back , но память выделяется в кусках, поэтому она предшествует конструкции элемента. И, следовательно, vector::erase должен уничтожать элементы, но необязательно он освобождает память (особенно если новый push_back должен скоро произойти …).

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

    Это плохой дизайн, если это происходит случайно вокруг кода, это хороший дизайн, если он происходит локально для classов, специально предназначенных для этой цели.

    Нет, зависит от ситуации, иногда это законный и хороший дизайн.

    Чтобы понять, почему и когда вам нужно явно называть деструкторов, давайте посмотрим, что происходит с «новым» и «удалить».

    Для динамического создания объекта T* t = new T; под капотом: 1. выделяется память sizeof (T). 2. Конструктор T вызывается для инициализации выделенной памяти. Оператор new выполняет две функции: выделение и инициализацию.

    Чтобы уничтожить объект delete t; под капотом: 1. Деструктор Т называется. 2. освобождается выделенная память для этого объекта. оператор delete также выполняет две функции: уничтожение и освобождение.

    Один пишет конструктор для инициализации, а деструктор – для уничтожения. Когда вы явно вызываете деструктор, выполняется только уничтожение, но не освобождение .

    Таким образом, законное использование явно вызывающего деструктора может быть «Я только хочу уничтожить объект, но я не могу (или не могу) освободить выделение памяти (пока)».

    Общим примером этого является предварительная выделение памяти для пула определенных объектов, которые в противном случае должны быть распределены динамически.

    При создании нового объекта вы получаете кусок памяти из предварительно выделенного пула и выполняете «размещение нового». После выполнения объекта вы можете явно вызвать деструктор, чтобы завершить работу по очистке, если таковая имеется. Но вы фактически не освободите память, как это сделал бы оператор delete. Вместо этого вы возвращаете кусок в пул для повторного использования.

    Как указано в FAQ, вы должны вызвать деструктор явно при использовании нового места размещения .

    Это единственный раз, когда вы когда-либо называете деструктор явным образом.

    Я согласен с тем, что это редко необходимо.

    Нет, вы не должны называть его явно, потому что он будет вызываться дважды. Один раз для ручного вызова и в другое время, когда заканчивается область, в которой объявлен объект.

    Например.

     { Class c; c.~Class(); } 

    Если вам действительно необходимо выполнить одни и те же операции, у вас должен быть отдельный метод.

    Существует определенная ситуация, при которой вы можете вызвать деструктор на динамически распределенном объекте с new местом размещения, но он не звучит так, как вам когда-либо понадобится.

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

    Бывают случаи, когда они необходимы:

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

      void destroy (pointer p) { // destroy objects by calling their destructor p->~T(); } 

    а в конструкции:

      void construct (pointer p, const T& value) { // initialize memory with placement new #undef new ::new((PVOID)p) T(value); } 

    также выполняется выделение в allocate () и освобождении памяти в deallocate (), используя механизмы распределения и деблокировки платформы. Этот распределитель использовался для обхода doug lea malloc и использования непосредственно, например LocalAlloc, в windowsх.

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

     class MyClass { HANDLE h1,h2; public: MyClass() { // handles have to be created first h1=SomeAPIToCreateA(); h2=SomeAPIToCreateB(); ... try { if(error) { throw MyException(); } } catch(...) { this->~MyClass(); throw; } } ~MyClass() { SomeAPIToDestroyA(h1); SomeAPIToDestroyB(h2); } }; 

    Я никогда не сталкивался с ситуацией, когда нужно вызвать деструктор вручную. Кажется, я помню, что даже Страуструп утверждает, что это плохая практика.

    Я нашел 3 раза, где мне нужно было это сделать:

    • выделение / деаллокация объектов в памяти, созданных с помощью памяти-карты или общей памяти
    • при реализации данного C-интерфейса с использованием C ++ (да, это до сих пор происходит сегодня, к сожалению (потому что у меня нет достаточного влияния, чтобы его изменить))
    • при реализации classов распределителя

    Нашел другой пример, где вам нужно было бы вызвать деструктор (ы) вручную. Предположим, вы внедрили вариант-подобный class, который содержит один из нескольких типов данных:

     struct Variant { union { std::string str; int num; bool b; }; enum Type { Str, Int, Bool } type; }; 

    Если экземпляр Variant имел std::string , и теперь вы назначаете другой тип объединению, вы должны сначала уничтожить std::string . Компилятор не сделает это автоматически .

    Память не отличается от другого ресурса: вы должны взглянуть на http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11 -Style особенно на ту часть, где Бьярне говорит о RAII ( около ~ 30 минут)

    Все необходимые шаблоны (shared_ptr, unique_ptr, weak_ptr) являются частью стандартной библиотеки C ++ 11

    У меня есть другая ситуация, когда я думаю, что вполне разумно назвать деструктора.

    При написании метода типа «Сброс» для восстановления объекта в его исходное состояние вполне разумно вызвать деструктор для удаления старых данных, которые сбрасываются.

     class Widget { private: char* pDataText { NULL }; int idNumber { 0 }; public: void Setup() { pDataText = new char[100]; } ~Widget() { delete pDataText; } void Reset() { Widget blankWidget; this->~Widget(); // Manually delete the current object using the dtor *this = blankObject; // Copy a blank object to the this-object. } }; 
    Давайте будем гением компьютера.