Обработка памяти деструктором класса в C ++ - PullRequest
3 голосов
/ 14 мая 2010

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

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

Спасибо

Ответы [ 7 ]

4 голосов
/ 14 мая 2010

Я думаю, что вопрос в обратном порядке. Не думайте о том, что уничтожение объекта не делает: думайте о том, что делает .

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

Вы можете немного поспорить, делается ли это «неявным деструктором» или нет - если вы пишете деструктор, такие вещи все же случаются. Это часть самого процесса уничтожения объекта, а не часть кода деструктора как такового.

Итак, если ваше «соединение с файлом» является членом класса FILE *, то деструктор по умолчанию не освобождает его, потому что FILE * - это вещь C, и у него нет деструктора. Уничтожение ФАЙЛА * ничего не делает. Если ваше «соединение с файлом» является std::ofstream, то у него есть деструктор, и деструктор пытается сбросить и закрыть соединение.

У вектора есть деструктор, который высвобождает ресурсы вектора. Это означает, что в свою очередь элементы в векторе называются деструкторами.

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

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

Если вы обнаружите, что пишете деструктор на C ++, который должен освободить более чем одну вещь, то вы, вероятно, плохо спроектировали класс. Почти наверняка в другом месте в классе есть ошибки безопасности исключений: возможно сделать код правильно, но если у вас достаточно опыта, чтобы понять его правильно, то у вас достаточно опыта, чтобы упростить свою собственную жизнь, проектируя его иначе; ). Решением является написание классов с единственной обязанностью - управлять одним ресурсом - и использовать их в качестве переменных-членов. Или, лучше, найти библиотечные классы, которые управляют ресурсом, который вы используете. shared_ptr довольно гибкий.

"нужно уничтожить переменные без кучи"

В C ++ нет такой вещи, как переменные "heap" и "non-heap". Уничтожение указателя не освобождает указанную вещь, по той очевидной причине, что ничто в типе указателя не говорит вам, как была выделена вещь, на которую он указывает, или кто «владеет» ею.

4 голосов
/ 14 мая 2010

векторов и т. П. Сами освободятся, потому что их деструктор называется.

На самом деле ваша проблема будет связана со всем, что может вызвать проблему с висящим указателем (или дескриптором или чем-то еще), то есть все, что должно быть закрыто или удалено вручную, не будет скрытым деструктором. Все, что разрушает себя должным образом, будет в порядке. Вот почему идиома RAII так популярна:

3 голосов
/ 14 мая 2010

Вам нужно понять три вещи о деструкторах. Допустим, у вас есть объект t класса T с тремя членами.

class T
{
    A a;
    B b;
    C c;

    // ...

} t;
  1. Уничтожение t ВСЕГДА означает вызов следующих деструкторов в указанном порядке: ~T(), ~C(), ~B(), ~A(). Вы не можете влиять на эту семантику. Невозможно предотвратить уничтожение элементов. Это ВСЕГДА происходит, независимо от того, определяете ли вы ~T() вручную или генерирует его компилятор.

  2. Неявно генерируемые деструкторы ВСЕГДА запрещены . Однако, как указано в пункте 1, деструкторы ~C(), ~B() и ~A() будут по-прежнему выполняться.

  3. Деструкторы скалярных типов, особенно указатели в стиле C, ВСЕГДА запрещены . Это единственная причина, по которой вам почти всегда нужно писать деструктор (а также конструктор копирования и оператор присваивания) вручную, когда у вас есть член-указатель в стиле C - после завершения ~T() уничтожается член-указатель в стиле C НИЧЕГО не делает, поэтому любая очистка, предназначенная для pointees должна быть сделана в ~T().

Надеюсь, это прояснит для вас.

2 голосов
/ 14 мая 2010

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

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

В отношении векторов или любых других объектов типа класса; у всех классов есть деструктор, который заботится о выпуске своих данных. Деструктор объекта этого типа вызывается, когда он выходит из области видимости. Все основные типы данных, такие как: int, float, double, short, bool и т. Д., Выпускаются аналогичным образом.

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

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

В неявном деструкторе происходит следующее:

  • Каждая из переменных-членов класса имеет свои деструкторы, вызываемые по очереди.

В явном деструкторе происходит следующее:

  • Тело явного деструктора выполнено
  • Каждая из переменных-членов класса имеет свои деструкторы, вызываемые по очереди.

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

Теперь в качестве небольшого совета относительно управления объектами, выделенными из памяти, вы должны почти всегда использовать RAII (получение ресурсов - это инициализация). Суть этого в умных указателях, это указатели, которые удаляются корректно, когда они выходят из области видимости точно так же, как объекты, выделенные без кучи. Собственность становится проблемой, как только вы начинаете их использовать, но это история для другого дня. Хорошее место для начала с умными указателями - boost :: shared_ptr . (между прочим, если вы еще не получили поддержку Boost и пишете код на C ++, сделайте себе одолжение ...)

1 голос
/ 14 мая 2010

Неявный деструктор вызывает деструктор для всех переменных-членов.

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

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

Деструкторы членов по умолчанию все еще называются, поэтому векторы, auto_ptrs, файлы, открытые с использованием потоков std или других библиотек C ++, уничтожаются. Мораль заключается в том, чтобы обернуть любые объекты ОС, которые должны быть освобождены, в классы C ++, которые приводят себя в порядок, чтобы классы приложений не беспокоились об этом.

0 голосов
/ 14 мая 2010

Вы уже делаете неправильные предположения. Если мой класс содержит данные, выделенные в куче, используя std::auto_ptr<Data>, неявный dtor будет обрабатывать его. Нет памяти будет просочиться. Причина в том, что имплицирующий dtor, как и любой другой dtor, будет вызывать dtors всех членов и базовых классов.

Теперь, хороший дизайн класса содержит все важные ресурсы как члены с соответствующими dtors. В результате тело dtor ничего не должно делать для предотвращения утечек ресурсов. Конечно, есть одно исключение из этого правила: сами классы управления ресурсами управляют только одним ресурсом и поэтому очищают этот ресурс в своем dtor.

0 голосов
/ 14 мая 2010

Деструктор класса неявно вызовет деструкторы всех нестатических членов, затем деструкторы виртуальных базовых классов, а затем свой собственный код.

Если в качестве членов класса у вас есть контейнеры STL, вам не нужно ничего делать явно - они освободят собственную память в своих деструкторах.

...