#c #oop #constructor #destructor
#c #ооп #конструктор #деструктор
Вопрос:
В следующей программе мы создаем объект Circle в локальной области видимости, потому что мы не используем ключевое слово new. Мы знаем, что память переменной или объекта автоматически завершается завершением в конце программы, чем почему мы используем destruct ?
#include<iostream>
using namespace std;
class Circle //specify a class
{
private :
double radius; //class data members
public:
Circle() //default constructor
{
radius = 0;
}
void setRadius(double r) //function to set data
{
radius = r;
}
double getArea()
{
return 3.14 * radius * radius;
}
~Circle() //destructor
{}
};
int main()
{
Circle c; //defalut constructor invoked
cout << c.getArea()<<endl;
return 0;
}
Комментарии:
1.
while (getNextInput()) {MemoryLeak leak; doStuff(leak);}
. Этого достаточно, чтобы убедить вас? Если нет, то как насчетUnsafeDatabaseConnection conn;
? Упс, это не очищается при завершении программы. Также обратите внимание, чтоCircle
у этого нет причин для объявленного пользователем деструктора.2. Иногда программы выполняются дольше секунды… И ваш объект использует не использует никаких ресурсов, но он может использовать ресурсы, такие как память, файлы, сетевые сокеты и т.д., Которые ограничены.
3. Даже если вы выделяете внутри main, вы хотите освободить память, чтобы избежать попадания в плохую привычку и упростить отладку утечек памяти (например, с помощью valgrind). (Кстати, ваш пример кода здесь совершенно бесполезен)
4. Не все объекты живут до конца
main()
. В вашем примере это так, но вряд ли это пример для всех программ.5. msvc 2013 с оптимизацией по размеру превращает это в жестко запрограммированный std :: cout 🙂
Ответ №1:
Считать память бесконечным ресурсом ОЧЕНЬ опасно. Подумайте о приложении реального времени, которое должно работать 24×7 и прослушивать поток данных с высокой скоростью (скажем, 1000 сообщений в секунду). Каждое сообщение имеет размер около 1 КБ, и каждый раз оно выделяет новый блок памяти (очевидно, в куче) для каждого сообщения. В целом нам нужно около 82 ГБ в день. Если вы не управляете своей памятью, теперь вы можете увидеть, что произойдет. Я не говорю о сложных методах пула памяти или подобных. С помощью простого арифметического вычисления мы видим, что мы не можем сохранить все сообщения в памяти. Это еще один пример, о котором вы подумали об управлении памятью (как с точки зрения выделения, так и с точки зрения освобождения).
Ответ №2:
Ну, во-первых, вам не нужно явно определять деструктор. Один из них будет автоматически определен компилятором. В качестве примечания, если вы это сделаете, по правилу 3 или 5 в c 11, если вы объявляете любой из следующих: конструктор копирования, назначение копирования, конструктор перемещения (c 11), назначение перемещения (c 11) или деструктор, вы должны явно определить их все.
Двигаемся дальше. Упрощенный принцип RAII гласит, что каждый выделенный ресурс должен быть освобожден. Кроме того, для каждого выделенного ресурса должен существовать один и только один владелец, объект, ответственный за распределение ресурса. Это и есть управление ресурсами. Ресурс здесь может означать все, что должно быть инициализировано перед использованием и освобождено после использования, например, динамически выделяемая память, системный обработчик (обработчики файлов, обработчики потоков), сокеты и т.д. Это достигается с помощью конструкторов и деструкторов. Если ваш объект несет ответственность за уничтожение ресурса, то ресурс должен быть уничтожен, когда ваш объект умрет. Здесь вступает в игру деструктор.
Ваш пример не так уж хорош, так как ваша переменная живет в main, поэтому она будет жить всю программу целиком.
Рассмотрим локальную переменную внутри функции:
int f()
{
Circle c;
// whatever
return 0;
}
Каждый раз, когда вы вызываете функцию f
, создается новый Circle
объект, который уничтожается при возврате функции.
Теперь в качестве упражнения рассмотрим, что не так со следующей программой:
std::vector foo() {
int *v = new int[100];
std::vector<int> result(100);
for (int i = 0; i < 100; i) {
v[i] = i * 100 5;
}
//
// .. some code
//
for (int i = 0; i < 100; i) {
result.at(i) = v[i];
}
bar(result);
delete v;
return resu<
}
Теперь это довольно бесполезная программа. Однако рассмотрите это с точки зрения корректности. Вы выделяете массив из 100 целых чисел в начале функции, а затем освобождаете их в конце функции. Так что вы можете подумать, что это нормально и никаких утечек памяти не происходит. Вы не могли бы ошибаться сильнее. Помните RAII? Кто несет ответственность за этот ресурс? функция foo
? Если это так, то он делает это очень плохо. Посмотрите на это еще раз:
std::vector foo() {
int *v = new int[100];
std::vector<int> result(100); <-- might throw
for (int i = 0; i < 100; i) {
v[i] = i * 100 5;
}
//
// .. some code <-- might throw in many places
//
for (int i = 0; i < 100; i) {
result.at(i) = v[i]; <-- might (theoretically at least) throw
}
bar(result); <-- might throw
delete v;
return resu<
}
Если в какой-то момент функция выдает ошибку, delete v
она не будет достигнута, и ресурс никогда не будет удален. Таким образом, у вас должен быть четкий владелец ресурса, ответственный за уничтожение этого ресурса. Как вы знаете, конструкторы и деструкторы помогут нам:
class Responsible() { // looks familiar? take a look at unique_ptr
private:
int * p_ = nullptr;
public:
Responsible(std::size_t size) {
p_ = new int[size];
}
~Responsible() {
delete p_;
}
// access methods (getters and setter)
};
Таким образом, программа становится:
std::vector foo() {
Responsible v(100);
//
// .. some code
//
return resu<
}
Теперь, даже если функция выбросит ресурс, он будет управляться должным образом, потому что при возникновении исключения стек разматывается, то есть все локальные переменные уничтожаются хорошо… к счастью для нас, Responsible
будет вызван деструктор will.
Ответ №3:
Ну, иногда у вашего объекта могут быть указатели или что-то, что нужно освободить или тому подобное.
Например, если у вас есть указатель в вашем классе Circle, вам нужно освободить его, чтобы избежать утечки памяти.
По крайней мере, так я знаю.