Из часто задаваемых вопросов по С++: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9
Помните: delete p делает две вещи: вызывает деструктор и освобождает память.
Если delete освобождает память, то зачем тут деструктор?
Из часто задаваемых вопросов по С++: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9
Помните: delete p делает две вещи: вызывает деструктор и освобождает память.
Если delete освобождает память, то зачем тут деструктор?
Вам нужно вызвать деструктор, если есть другие вещи, которые необходимо сделать, кроме простого освобождения памяти.
Помимо очень простых классов, обычно они есть.
Такие вещи, как закрытие файловых дескрипторов или отключение соединений с базой данных, удаление других объектов, на которые указывают данные-члены в вашем объекте, и так далее.
Классический пример — реализация стека:
class myStack {
private:
int *stackData;
int topOfStack;
public:
void myStack () {
topOfStack = 0;
stackData = new int[100];
}
void ~myStack () {
delete [] stackData;
}
// Other stuff here like pop(), push() and so on.
}
Теперь подумайте, что произойдет, если деструктор не будет вызываться каждый раз при удалении одного из ваших стеков. В этом случае в C++ нет автоматической сборки мусора, поэтому stackData
память будет утекать, и вы в конечном итоге закончите.
Это требование к деструктору удалить все свои ресурсы перемещает вниз по дереву к базовым типам. Например, у вас может быть пул соединений с базой данных с массивом соединений с базой данных. Деструктор для этого будет delete
каждое отдельное соединение с базой данных.
Одно соединение с базой данных может выделять множество вещей, таких как буферы данных, кэши, скомпилированные запросы SQL и так далее. Таким образом, деструктор для подключения к базе данных также должен delete
выполнять все эти действия.
Другими словами, у вас есть что-то вроде:
+-------------------------------------+
| DB connection pool |
| |
| +-------------------------+---+---+ |
| | Array of DB connections | | | |
| +-------------------------+---+---+ |
| | | |
+-----------------------------|---|---+
| | +---------+
| +-> | DB Conn |
+---------+ | +---------+
| DB Conn | <----+ / | \
+---------+ buffers | queries
/ | \ caches
buffers | queries
caches
Освобождение памяти для пула соединений с БД не повлияет на существование отдельного соединения с БД или других объектов, на которые они указывают.
Вот почему я упомянул, что только простые классы могут обойтись без деструктора, и это классы, которые, как правило, появляются в нижней части этого дерева выше.
Класс вроде:
class intWrapper {
private:
int value;
public:
intWrapper () { value = 0; }
~intWrapper() {}
void setValue (int newval) { value = newval; }
int getValue (void) { return value; }
}
не имеет реальной необходимости в деструкторе, так как освобождение памяти — это все, что вам нужно сделать.
Суть в том, что new
и delete
являются противоположными концами одного и того же полюса. Вызов new
сначала выделяет память, а затем вызывает соответствующий код конструктора, чтобы привести ваш объект в работоспособное состояние.
Затем, когда вы закончите, delete
вызывает деструктор, чтобы "снести" ваш объект, восстанавливая память, выделенную для этого объекта.
so the stackData memory would leak and you'd eventually run out.
Ничего не понял. Удалить освобождает память, поэтому вызов удаления в стеке освободит ее, так как же происходят утечки памяти?
- person Aquarius_Girl; 03.02.2012
delete
), которые может потребоваться выполнить в рамках этой очистки (например, закрытие устаревших библиотек C, которые не используют объекты).
- person paxdiablo; 03.02.2012
has no real need for a destructor since the memory deallocation is all you need to do.
В этом классе вы не выделяли никакой памяти, поэтому мы не должны ничего освобождать. Деструктор все равно понадобится для уничтожения объекта intWrapper a;
? Правильно?
- person Aquarius_Girl; 04.02.2012
delete [] arr
вызовет деструктор один раз для каждого объекта в массиве, но может освободить только один блок памяти.
- person paxdiablo; 04.02.2012
You need to understand that memory allocation/deallocation and construction/destruction are two different things,
Ну, я действительно НЕ понимаю, насколько это разные вещи. Ах! Вы имеете в виду, что конструкция относится к руководству new
, а выделение относится к таким объектам, как classX objX;
?
- person Aquarius_Girl; 04.02.2012
new
делает две вещи. Первый — выделить память (например, malloc
в C) для вашего объекта, второй — вызвать конструктор, чтобы вы могли настроить объект. С другой стороны, delete
также выполняет две вещи. Он вызывает деструктор, чтобы вы могли уничтожить свой объект, а затем он освобождает память (а-ля C's free
). Я не уверен, что смогу объяснить это яснее, и комментарии заполняются, поэтому, если это все еще не ясно, вы можете подумать о том, чтобы задать новый вопрос только по этому аспекту.
- person paxdiablo; 04.02.2012
Если delete освобождает память, то зачем тут деструктор?
Смысл деструктора в том, чтобы выполнить любую логику, необходимую для очистки после вашего объекта, например:
new
), и вы хотите, чтобы они удалялись вместе с вашим объектом, вы должны удалить их в своем деструкторе. См. ответ paxdiablos.
- person Stephan; 03.02.2012
Предположим, у вас есть класс, который динамически выделяет память:
class something {
public:
something() {
p = new int;
}
~something() {
delete p;
}
int *p;
};
Теперь давайте динамически выделим объект something
:
something *s = new something();
delete s;
Теперь, если бы delete
не вызывал деструктор, то s->p
никогда не освободился бы. Таким образом, delete
должен вызвать деструктор и затем освободить память.
Деструктор отвечает за освобождение ресурсов, отличных от выделенной памяти объекта. Например, если объект имеет открытый дескриптор файла, деструктор может вызвать для него fclose
.
Он освобождает память, занимаемую этим объектом. Однако обо всем, что было выделено объектом (и принадлежит этому объекту), необходимо позаботиться в деструкторе.
Тоже в общем... FAQ... обычно не ошибаются.
если вы объявляете класс обычным (не указателем), он автоматически вызывает конструктор и автоматически вызывает деструктор при закрытии программы. Если вы объявляете как указатель, он вызывает конструктор при инициализации с использованием нового и не вызывает деструктор автоматически, пока вы не вызовете удаление этого указателя с помощью удаления
Деструктор предназначен для очистки изменений, которые конструктор объекта и функции-члены могли внести в состояние программы. Это может быть что угодно — удалить объект из какого-то глобального списка, закрыть открытый файл, освободить выделенную память, закрыть соединение с базой данных и т. д.
Деструктор не был бы обязательной функцией. Такие языки, как C, Java, C#, не имеют деструкторов. C++ также может жить без него.
Деструктор — это специальное средство, предоставляемое C++ (такое же, как конструктор). Он вызывается, когда объект "уничтожается".
Уничтожить означает, что область действия объекта официально завершена, и любая ссылка на этот объект будет недопустимой. Например:
A* foo ()
{
static A obj; // 'A' is some class
A *p = &obj;
return p;
}
В приведенном выше коде obj
представляет собой static
данные, созданные типа A
; foo()
возвращает ссылку на этот obj
, что нормально, потому что obj.~A()
еще не вызывается. Предположим, что obj
не является статическим. Код будет скомпилирован, однако A*
, возвращенный foo()
, теперь указывает на ячейку памяти, которая больше не является объектом A
. Значит -> операция плохая/незаконная.
Теперь вы должны уметь отличать освобождение памяти от уничтожения объекта. Оба тесно связаны, но есть тонкая грань.
Также помните, что деструктор можно вызывать в нескольких местах:
int bar ()
{
A obj;
...
return 0; // obj.~A() called here
...
return 1; // obj.~A() called here
...
return 2; // obj.~A() called here
}
В приведенном выше примере obj.~A()
будет вызываться только один раз, но его можно вызвать из любого из показанных мест.
Во время разрушения вы можете захотеть сделать несколько полезных вещей. Допустим, class A
вычисляет какой-то результат, когда объект уничтожается; он должен напечатать результат вычисления. Это можно сделать в стиле C
(поместив некоторую функцию в каждый оператор return
). Но ~A()
— это доступное универсальное средство.
but it can be called from any of the places shown
Как это возможно? Когда встречается return 0
, функция возвращается, и оставшийся код НИКОГДА не может быть выполнен, не так ли?
- person Aquarius_Girl; 03.02.2012
obj.~A()
вызывается только один раз, но компилятор выдает его код во всех показанных выше местах. Потому что компилятор не знает, что во время выполнения будет выполняться return
.
- person iammilind; 03.02.2012
В дополнение к ответам, сосредоточенным на объектах, выделенных в куче (с помощью new; которые освобождаются только с помощью delete)... Не забывайте, что если вы поместите объект в стеке (то есть без использования new), его деструктор будет вызван автоматически и он будет удален из стека (без вызова delete ), когда он выходит за рамки. Таким образом, у вас есть одна функция, которая гарантировано будет выполнена, когда объект выходит из области видимости, и это идеальное место для выполнения очистки всех других ресурсов, выделенных объектом (различные дескрипторы, сокеты... и объекты, созданные в куче этим объектом - если они не должны пережить этот). Это используется в идиоме RAII.
std::string
иstd::vector
и т. д.), вам не нужно писать деструктор самостоятельно. Если он не работает правильно (если ваши членыchar*
илиFILE*
или кто-то еще), вы должны написать деструктор, делающий правильные вещи. (Обратите внимание, что удаление указателя ничего не делает.) Поэтому, если вы хотите выполнитьdelete member_pointer
до того, как объект умрет, вы должны явно указать так в деструкторе. Подробности см. в этих часто задаваемых вопросах. - person fredoverflow   schedule 03.02.2012