Поскольку оператор удаления освобождает память, зачем мне деструктор? - PullRequest
6 голосов
/ 03 февраля 2012

из c ++ FAQ: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.9

Помните: delete p выполняет две функции: вызывает деструктор и освобождает память.

Если удаление освобождает память, тогда зачем нужен деструктор?

Ответы [ 9 ]

13 голосов
/ 03 февраля 2012

Если удаление освобождает память, то зачем здесь деструктор?

Задача деструктора - выполнить любую логику, необходимую для очистки после вашего объекта, например:

  • вызов удаления для других объектов, принадлежащих уничтожаемому объекту
  • , должным образом освобождая другие ресурсы, такие как соединения с базой данных;файловые дескрипторы и тому подобное
9 голосов
/ 03 февраля 2012

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

Кроме очень простых классов, обычно есть.

Такие вещи, как закрытие файловых дескрипторов или закрытие соединений с базой данных, удаление других объектов, на которые указывают данные членов в вашем объекте, и т. Д.

Классическим примером является реализация стека:

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 вызывает деструктор, чтобы «разрушить» ваш объект, освобождает память, выделенную для этого объекта.

3 голосов
/ 03 февраля 2012

Предположим, у вас есть класс, который динамически выделяет память:

class something {
public:
    something() {
        p = new int;
    }

    ~something() {
        delete p;
    }

    int *p;
};

Теперь давайте динамически распределим объект something:

something *s = new something();

delete s;

Теперь, если delete didn 'Если ты не вызовешь деструктора, то s->p никогда не освободится.Так что delete должен вызвать деструктор и затем освободить память.

2 голосов
/ 03 февраля 2012

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

1 голос
/ 03 февраля 2012

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

Также, в общем ... Часто задаваемые вопросы ...как правило, не так.

0 голосов
/ 03 февраля 2012

В дополнение к ответам, сосредоточенным на объектах, выделенных в куче (с new ; которые освобождаются только с delete ) ... Не забывайте, что если вы поместите объект в стеке (то есть, без использования new ), его деструктор будет называться автоматически и будет удален из стека (без вызова delete ) при это выходит за рамки. Таким образом, у вас есть одна функция, которая гарантированно будет выполняться, когда объект выходит из области видимости, и которая является идеальным местом для выполнения очистки всех других ресурсов, выделенных объектом (различные дескрипторы, сокеты ... и объекты созданный в куче этим объектом - если они не должны пережить этот). Это используется в идиоме RAII .

0 голосов
/ 03 февраля 2012

Деструктор не был бы обязательной функцией .Такие языки, как, C, Java, C # не имеют деструкторов.C ++ также может жить без него.

Деструктор - это специальное средство , предоставляемое C ++ (аналогично Constructor).Он вызывается, когда объект «уничтожен» .

Уничтожить означает, что область действия объекта официально завершена и любая ссылка на этот объект будет недопустимой.Например:

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() - это легкодоступное универсальное средство.

0 голосов
/ 03 февраля 2012

Деструктор предназначен для очистки изменений, которые конструктор объекта и функции-члены могли внести в состояние программы. Это может быть что угодно - удалить объект из какого-либо глобального списка, закрыть открытый файл, освободить выделенную память, закрыть соединение с базой данных и т. Д.

0 голосов
/ 03 февраля 2012

если вы объявляете класс нормальным (не указателем), он автоматически вызывает конструктор и вызывает деструктор автоматически при закрытии программы.Если вы объявляете указатель, он вызывает конструктор при инициализации с использованием new и не вызывает деструктор автоматически, пока вы не вызовете delete этот указатель с помощью delete

...