Деструктор — это функция-член, которая вызывается автоматически, когда объект выходит за пределы области или явно уничтожается вызовом delete . Деструктор имеет то же имя, что и класс , перед которым предшествует тильда ( ~ ). Например, деструктор для класса String объявляется следующим образом: ~String() .
Если деструктор не определен, компилятор предоставляет его по умолчанию; для многих классов этого достаточно. Необходимо определить пользовательский деструктор, только если класс хранит дескрипторы системных ресурсов, которые необходимо освободить, или указателей, владеющих памятью, на которую они указывают.
Рассмотрим следующее объявление класса String :
// spec1_destructors.cpp #include class String < public: String( char *ch ); // Declare constructor ~String(); // and destructor. private: char *_text; size_t sizeOfText; >; // Define the constructor. String::String( char *ch ) < sizeOfText = strlen( ch ) + 1; // Dynamically allocate the correct amount of memory. _text = new char[ sizeOfText ]; // If the allocation succeeds, copy the initialization string. if( _text ) strcpy_s( _text, sizeOfText, ch ); >// Define the destructor. String::~String() < // Deallocate the memory that was previously reserved // for this string. delete[] _text; >int main()
В предыдущем примере деструктор String::~String использует delete оператор для освобождения пространства, динамически выделенного для хранения текста.
new c++ что это. new c++ пример. c++ new delete. delete c++ что это. delete c++ пример. Урок #53
Объявление деструкторов
Деструкторы — это функции с тем же именем, что и класс, но с добавленным в начало знаком тильды ( ~ ).
При объявлении деструкторов действуют несколько правил. Деструкторы:
- Не принимать аргументы.
- Не возвращайте значение (или void ).
- Не может быть объявлен как const , volatile или static . Однако их можно вызывать для уничтожения объектов, объявленных как const , volatile или static .
- Может быть объявлен как virtual . С помощью виртуальных деструкторов можно уничтожать объекты, не зная их тип. Правильный деструктор для объекта вызывается с помощью механизма виртуальной функции. Деструкторы также можно объявить как чистые виртуальные функции для абстрактных классов.
Использование деструкторов
Деструкторы вызываются, когда происходит одно из следующих событий:
- Локальный (автоматический) объект с областью видимости блока выходит за пределы области видимости.
- Объект, выделенный new с помощью оператора , явным образом освобождается с помощью delete .
- Время существования временного объекта заканчивается.
- Программа заканчивается, глобальные или статические объекты продолжают существовать.
- Деструктор явно вызываться с использованием полного имени функции деструктора.
Деструкторы могут свободно вызывать функции-члена класса и осуществлять доступ к данным членов класса.
Существует два ограничения на использование деструкторов:
- Вы не можете взять его адрес.
- Производные классы не наследуют деструктор базового класса.
Порядок уничтожения
Когда объект выходит за пределы области или удаляется, последовательность событий при его полном уничтожении выглядит следующим образом:
Убирай ОБЪЕКТЫ на видео ЛЕГКО — ЭТО ТЕБЯ ШОКИРУЕТ! (Обучение After Effects) | ASUS STUDIO
- Вызывается деструктор класса, и выполняется тело функции деструктора.
- Деструкторы для объектов нестатических членов вызываются в порядке, обратном порядку их появления в объявлении класса. Необязательный список инициализации элементов, используемый при создании этих элементов, не влияет на порядок построения или уничтожения.
- Деструкторы для не виртуальных базовых классов вызываются в обратном порядке объявления.
- Деструкторы для виртуальных базовых классов вызываются в порядке, обратном порядку их объявления.
// order_of_destruction.cpp #include struct A1 < virtual ~A1() < printf(«A1 dtorn»); >>; struct A2 : A1 < virtual ~A2() < printf(«A2 dtorn»); >>; struct A3 : A2 < virtual ~A3() < printf(«A3 dtorn»); >>; struct B1 < ~B1() < printf(«B1 dtorn»); >>; struct B2 : B1 < ~B2() < printf(«B2 dtorn»); >>; struct B3 : B2 < ~B3() < printf(«B3 dtorn»); >>; int main() < A1 * a = new A3; delete a; printf(«n»); B1 * b = new B3; delete b; printf(«n»); B3 * b2 = new B3; delete b2; >Output: A3 dtor A2 dtor A1 dtor B1 dtor B3 dtor B2 dtor B1 dtor
Виртуальные базовые классы
Деструкторы для виртуальных базовых классов вызываются в порядке, обратном их указанию в направленном ациклическом графе (в глубину, слева направо, обход в обратном порядке). На следующем рисунке представлен граф наследования.
Пять классов с метками A–E расположены в графе наследования. Класс E является базовым классом B, C и D. Классы C и D являются базовым классом A и B.
Ниже перечислены заголовки классов, представленных на рисунке.
class A class B class C : virtual public A, virtual public B class D : virtual public A, virtual public B class E : public C, public D, virtual public B
Чтобы определить порядок удаления виртуальных базовых классов объекта типа E , компилятор выполняет сборку списка, применяя следующий алгоритм.
- Просмотрите левую часть графа, начиная с самой глубокой точки графа (в данном случае E ).
- Просматривайте граф справа налево, пока не будут пройдены все узлы. Запомните имя текущего узла.
- Пересмотрите предыдущий узел (вниз и вправо), чтобы определить, является ли рассматриваемый узел виртуальным базовым классом.
- Если рассматриваемый узел является виртуальным базовым классом, просмотрите список, чтобы проверить, был ли он введен ранее. Если это не виртуальный базовый класс, игнорируйте его.
- Если запомнированного узла еще нет в списке, добавьте его в нижнюю часть списка.
- Просмотрите граф вверх и вдоль следующего пути вправо.
- Перейдите к шагу 2.
- Если путь последний путь вверх исчерпан, запомните имя текущего узла.
- Перейдите к шагу 3.
- Выполняйте этот процесс, пока нижний узел снова не станет текущим узлом.
Таким образом, для класса E порядок удаления будет следующим.
- Не виртуальный базовый класс E .
- Не виртуальный базовый класс D .
- Не виртуальный базовый класс C .
- Виртуальный базовый класс B .
- Виртуальный базовый класс A .
В ходе этого процесса создается упорядоченный список уникальных записей. Имя класса никогда не отображается дважды. После создания списка он выполняется в обратном порядке, и вызывается деструктор для каждого из классов в списке от последнего к первому.
Порядок построения или уничтожения в первую очередь важен, когда конструкторы или деструкторы в одном классе полагаются на то, что другой компонент создается первым или сохраняется дольше, например, если деструктор для A (на рисунке выше) полагался на B то, что он все еще присутствует при выполнении кода, или наоборот.
Такие взаимозависимости между классами в графе наследования опасны, поскольку классы, наследуемые впоследствии, могут изменить крайний левый путь, тем самым изменив порядок построения и удаления.
Не виртуальные базовые классы
Деструкторы для не виртуальных базовых классов вызываются в обратном порядке, в котором объявляются имена базовых классов. Рассмотрим следующее объявление класса.
class MultInherit : public Base1, public Base2 .
В предыдущем примере деструктор Base2 вызывается перед деструктором Base1 .
Явные вызовы деструктора
Редко возникает необходимость в явном вызове деструктора. Однако может быть полезно выполнить удаление объектов, размещенных по абсолютным адресам. Эти объекты обычно выделяются с помощью определяемого new пользователем оператора, который принимает аргумент размещения.
Оператор delete не может освободить эту память, так как она не выделена из свободного хранилища (дополнительные сведения см. в разделе Операторы создания и удаления). Вызов деструктора, однако, может выполнить соответствующую очистку. Для явного вызова деструктора для объекта ( s ) класса String воспользуйтесь одним из следующих операторов.
s.String::~String(); // non-virtual call ps->String::~String(); // non-virtual call s.~String(); // Virtual call ps->~String(); // Virtual call
Нотация для явных вызовов деструкторов, показанная в предыдущем примере, может использоваться независимо от того, определяет ли тип деструктор. Это позволяет выполнять такие явные вызовы, не зная, определен ли деструктор для типа. Явный вызов деструктора, если ни один из них не определен, не имеет никакого эффекта.
Отказоустойчивость
Классу требуется деструктор, если он получает ресурс, и для безопасного управления ресурсом ему, вероятно, необходимо реализовать конструктор копирования и назначение копии.
Если эти специальные функции не определены пользователем, они неявно определяются компилятором. Неявно созданные конструкторы и операторы присваивания выполняют неглубокое копирование по члену, что почти наверняка неправильно, если объект управляет ресурсом.
В следующем примере неявно созданный конструктор копирования сделает указатели str1.text и будет ссылаться на одну и str2.text ту же память, а когда мы вернемся из copy_strings() , эта память будет удалена дважды, что является неопределенным поведением:
void copy_strings() < String str1(«I have a sense of impending disaster. «); String str2 = str1; // str1.text and str2.text now refer to the same object >// delete[] _text; deallocates the same memory twice // undefined behavior
Явное определение деструктора, конструктора копирования или оператора присваивания копии предотвращает неявное определение конструктора перемещения и оператора присваивания перемещения. В этом случае невозможность предоставления операций перемещения обычно является упущенной возможностью оптимизации, если копирование является дорогостоящим.
Источник: learn.microsoft.com
Как удалить объект класса который больше не нужен в программе
Деструктор выполняет освобождение использованных объектом ресурсов и удаление нестатических переменных объекта. Деструктор автоматически вызывается, когда удаляется объект. Удаление объекта происходит в следующих случаях:
- когда завершается выполнение области видимости, внутри которой определены объекты
- когда удаляется контейнер (например, массив), который содержит объекты
- когда удаляется объект, в котором определены переменные, представляющие другие объекты
- динамически созданные объекты удаляются при применении к указателю на объект оператора delete
По сути деструктор — это функция, которая называется по имени класса (как и конструктор) и перед которой стоит тильда (~):
~имя_класса() < // код деструктора >
Деструктор не имеет возвращаемого значения и не принимает параметров. Каждый класс может иметь только один деструктор.
Обычно деструктор не так часто требуется и в основном используется для освобождения связанных ресурсов. Например, объект класса использует некоторый файл, и в деструкторе можно определить код закрытия файла. Или если в классе выделяется память с помощью оператора new , то в деструкторе можно освободить подобную память.
Сначала рассмотрим простейшее определение деструктора:
#include class Person < public: Person(std::string p_name) < name = p_name; std::cout ~Person() < std::cout private: std::string name; >; int main() < < Person tom; Person bob; > // объекты Tom и Bob уничтожаются Person sam; > // объект Sam уничтожается
В классе Person определен деструктор, который просто уведомляет об уничтожении объекта:
~Person()
В функции main создаются три объекта Person. Причем два из них создается во вложенном блоке кода.:
< Person tom; Person bob; >
Этот блок кода задается границы области видимости, в которой существуют эти объекты. И когда выполнение блока завершается, то уничтожаются обе переменных, и для обоих объектов вызываются деструкторы.
После этого создается третий объект — sam
int main() < < Person tom; Person bob; > // объекты Tom и Bob уничтожаются Person sam; > // объект Sam уничтожается
Поскольку он определен в контексте функции main, то и уничтожается при завершении этой функции. В итоге мы получим следующий консольный вывод:
Person Tom created Person Bob created Person Bob deleted Person Tom deleted Person Sam created Person Sam deleted
Чуть более практический пример. Допустим, у нас есть счетчик объектов Person в виде статической переменной. И если в конструкторе при создании каждого нового объекта счетчик увеличивается, то в деструкторе мы можем уменьшать счетчик:
#include class Person < public: Person(std::string p_name) < name = p_name; ++count; std::cout ~Person() < —count; std::cout private: std::string name; static inline unsigned count<>; // счетчик объектов >; int main() < < Person tom; Person bob; > // объекты Tom и Bob уничтожаются Person sam; > // объект Sam уничтожается
Консольный вывод программы:
Person Tom created. Count: 1 Person Bob created. Count: 2 Person Bob deleted. Count: 1 Person Tom deleted. Count: 0 Person Sam created. Count: 1 Person Sam deleted. Count: 0
При этом выполнение самого деструктора еще не удаляет сам объект.
Непосредственно удаление объекта производится в ходе явной фазы удаления, которая следует после выполнения деструктора.
Стоит также отметить, что для любого класса, который не определяет собственный деструктор, компилятор сам создает деструктор. Например, если бы класс Person не имел бы явно определенного деструктора, то для него автоматически создавался бы следующий деструктор:
Источник: metanit.com
Как удалить объект класса?
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает. К нему по прежнему можно обратиться и вывести значения xy. При этом по завершению работы программы, в консоль выводится «destruct» второй раз. То есть объект действительно где-то висит и затем удаляется повторно.
Почему так происходит и как правильно уничтожить объект?
- Вопрос задан более трёх лет назад
- 4173 просмотра
Комментировать
Решения вопроса 1
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает.
Тебе предстоит узнать много нового. О том, что такое неопределённое поведение. О том, что удалять опреатором delete можно только объекты созданные оператором new. О том, что обращение к полям объекта после вызова деструктора — это неопределённое поведение, так же как и вызов деструктора больше чем один раз. О том, что это забота программиста — следовать правилам языка, а рантайм может закрывать глаза на ошибки, а может громко кричать и завершать программу при первой же возможности.
Она работает и выводит в консоль «destruct». Но после этого объект никуда не исчезает. К нему по прежнему можно обратиться и вывести значения xy. При этом по завершению работы программы, в консоль выводится «destruct» второй раз.
Ну а у меня твоя программа ожидаемо вылетает сразу же в месте вызова delete.
как правильно уничтожить объект?
Объект на стеке можно корректно уничтожить двумя способами: естественным — выйдя из блока в котором он определён, и насильственным — вызвав явно его деструктор и создав на его месте другой объект.
Источник: qna.habr.com