Я подумал: мол, если вы вызываете деструктор вручную - вы что-то делаете не так. Но всегда ли так? Есть контрпримеры? Ситуации, когда необходимо вызвать его вручную или когда этого трудно / невозможно / нецелесообразно избежать?
c++
coding-style
destructor
Фиолетовый жираф
источник
источник
new
для инициализации нового объекта вместо старого. Как правило, это не очень хорошая идея, но это не редкость.Ответы:
Вызов деструктора вручную требуется, если объект был создан с использованием перегруженной формы
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 при изменении типа представленного объекта. Кроме того, когда объединение разрушается, необходимо явно вызвать деструктор текущего объекта, если он требует уничтожения.
источник
operator new
», правильной фразой будет «использованиеplacement new
».operator new(std::size_t, void*)
(и вариации массива), но и обо всех перегруженных версияхoperator new()
.temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong
. Почему ты говоришь это? Я бы подумал, что если деструктор тривиален или близок к тривиальному, он имеет минимальные накладные расходы и увеличивает использование принципа DRY. Если использовать в таких случаях с ходомoperator=()
, это может быть даже лучше, чем использование свопа. YMMV.virtual
функции (virtual
функции не будут воссозданы), а в противном случае объект просто частично [воссоздан].Все ответы описывают конкретные случаи, но есть общий ответ:
Вы вызываете dtor явно каждый раз, когда вам нужно просто уничтожить объект (в смысле C ++), не освобождая память, в которой находится объект.
Обычно это происходит во всех ситуациях, когда выделение / освобождение памяти управляется независимо от создания / уничтожения объекта. В этих случаях построение происходит путем размещения new на существующем фрагменте памяти, а разрушение происходит посредством явного вызова 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
покупаются по умолчанию .Плохой дизайн, если это происходит случайно вокруг кода, хороший дизайн, если это происходит локально с классами, специально разработанными для этой цели.
источник
Нет, вы не должны вызывать его явно, потому что он будет вызываться дважды. Один раз для ручного вызова и другой раз, когда область, в которой объявлен объект, заканчивается.
Например.
Если вам действительно нужно выполнить одни и те же операции, вам нужен отдельный метод.
Существует конкретная ситуация, в которой вы можете захотеть вызвать деструктор для динамически выделяемого объекта с размещением,
new
но это не звучит как то, что вам когда-либо понадобится.источник
Нет, зависит от ситуации, иногда это законный и хороший дизайн.
Чтобы понять, почему и когда вам нужно явно вызывать деструкторы, давайте посмотрим, что происходит с «new» и «delete».
Чтобы создать объект динамически,
T* t = new T;
под капотом: 1. Выделяется память sizeof (T). 2. Конструктор T вызывается для инициализации выделенной памяти. Оператор new выполняет две функции: выделение и инициализацию.Чтобы уничтожить объект
delete t;
под капотом: 1. Вызывается деструктор T. 2. освобождается память, выделенная для этого объекта. оператор delete также выполняет две функции: уничтожение и освобождение.Один пишет конструктор для инициализации и деструктор для разрушения. Когда вы явно вызываете деструктор, выполняется только уничтожение, но не освобождение .
Таким образом, правомерное использование явного вызова деструктора может быть таким: «Я хочу только уничтожить объект, но я не (или не могу) освободить выделенную память (пока)».
Типичный пример этого - предварительное выделение памяти для пула определенных объектов, которые в противном случае должны выделяться динамически.
При создании нового объекта вы получаете кусок памяти из предварительно выделенного пула и выполняете «новое размещение». После завершения работы с объектом вы можете явно вызвать деструктор для завершения работы по очистке, если таковая имеется. Но на самом деле вы не будете освобождать память, как это сделал бы оператор delete. Вместо этого вы возвращаете фрагмент в пул для повторного использования.
источник
Как указано в FAQ, вы должны явно вызывать деструктор при использовании нового размещения .
Я согласен с тем, что это редко требуется.
источник
Каждый раз, когда вам нужно отделить выделение от инициализации, вам потребуется размещение нового и явного вызова деструктора вручную. Сегодня это редко необходимо, поскольку у нас есть стандартные контейнеры, но если вам нужно реализовать какой-то новый тип контейнера, он вам понадобится.
источник
Бывают случаи, когда они необходимы:
В коде, над которым я работаю, я использую явный вызов деструктора в распределителях, у меня есть реализация простого распределителя, который использует размещение 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 () с использованием механизмов выделения и освобождения памяти, зависящих от платформы. Этот распределитель использовался для обхода malloc и прямого использования, например, LocalAlloc в окнах.
источник
Я нашел 3 случая, когда мне нужно было это сделать:
источник
Я никогда не сталкивался с ситуацией, когда нужно вызывать деструктор вручную. Я, кажется, помню, что даже Страуструп утверждал, что это плохая практика.
источник
C+
☺Что насчет этого?
Деструктор не вызывается, если из конструктора генерируется исключение, поэтому мне приходится вызывать его вручную, чтобы уничтожить дескрипторы, которые были созданы в конструкторе перед исключением.
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); } };
источник
ctor
здесь, неверно, именно по той причине, которую вы указали сами: если распределение ресурсов не удается, возникает проблема с очисткой. "Ctor" не должен звонитьthis->~dtor()
.dtor
должен вызываться для построенных объектов, и в этом случае объект еще не построен. Что бы ни случилось,ctor
очистка должна выполняться. Внутриctor
кода вы должны использовать такие утилиты, какstd::unique_ptr
автоматическая очистка для вас в случаях, когда что-то бросает. ТакжеHANDLE h1, h2
неплохо было бы изменить поля в классе для поддержки автоматической очистки.MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }
и все . Никаких рискованных действий по очистке вручную, отсутствие хранения ручек в частично построенном объекте до тех пор, пока все не будет в безопасности, - это бонус. Если вы изменитеHANDLE h1,h2
класс наcleanupGuard<HANDLE> h1;
etc, то вам может вообще не понадобитьсяdtor
.cleanupGuard1
иcleanupGuard2
зависит от того, что дает релевантныйxxxToCreate
доход и какие параметрыxxxxToDestroy
принимают соответствующие значения . Если они простые, вам может даже не понадобиться ничего писать, так как часто оказывается, чтоstd::unique_ptr<x,deleter()>
(или аналогичный) может помочь вам в обоих случаях.Нашел еще один пример, в котором вам придется вызывать деструкторы вручную. Предположим, вы реализовали вариантный класс, содержащий один из нескольких типов данных:
struct Variant { union { std::string str; int num; bool b; }; enum Type { Str, Int, Bool } type; };
Если
Variant
экземпляр содержал astd::string
, а теперь вы назначаете объединению другой тип, вы должны уничтожитьstd::string
первый. Компилятор не сделает этого автоматически .источник
У меня есть другая ситуация, когда я считаю вполне разумным вызвать деструктор.
При написании метода типа «Reset» для восстановления объекта в его начальное состояние вполне разумно вызвать деструктор для удаления старых данных, которые сбрасываются.
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. } };
источник
cleanup()
метод, который будет вызываться в этом случае и в деструкторе?