А.В. Столяров - Введение в язык Си++ (1114949), страница 8
Текст из файла (страница 8)
е. на захваченный ресурс ссылаются толькозакрытые поля объекта (в примере с файлом это может означать, чтодескриптор открытого файла хранится в закрытом поле объекта). В случае, если объект по тем или иным причинам прекратит существование,ресурс так и останется захваченным.Решить проблему позволяет описание в классе объекта функции-деструктора. Деструктор — это метод класса, вызов которого автоматическивставляется компилятором в код в любой ситуации, когда объект прекращает существование. Функция-деструктор имеет имя, представляющеесобой имя описываемого типа (класса или структуры), к которому спереди добавлен знак ~ (тильда). Список параметров функции-деструкторавсегда пуст, т.
к. в языке отсутствуют средства передачи параметров деструктору. Поскольку деструктор играет специальную роль и в явномвиде не вызывается, тип возвращаемого значения для деструктора такжене указывается (деструктор никогда не возвращает никаких значений).Проиллюстрируем понятие деструктора на примере класса F ile , инкапсулирующего дескриптор файла.c la s s F ile {in t fd ; / / Дескриптор. -1 означает, что файла нетp u b lic :F ile O { fd = -1 ; }/ / Конструктор устанавливает отсутствие файлаbool OpenEO(const char *name) {fd = open(name, 0_ED0NLY);return (fd != - 1 );}/ / метод пытается открыть файл на чтение,/ / возвращает tru e в случае успеха,/ / f a ls e в случае неудачии .../ / .
. . методы работы с файлом . . .И ...~ F ile () { i f ( f d !=—1) c lo s e ( f d ) ; }34/ / Д еструктор закр ы вает ф айл, если он открытДеструктор будет вызван в любой ситуации, когда объект типа F ileпрекращает существование. Например, если объект был описан как локальный в функции, то при возврате из функции (в том числе и досрочном вызове оператора return) для этого объекта отработает деструктор.Вообще, при создании объекта ровно один р а з отр абаты ваеткон структор, при уничтожении о бъекта ровно один р аз отр абаты вает деструктор.§2.10. О перации работы с ди н ам ическойп ам ятьюИзвестно, что язык Си сам по себе не включает средств работы сдинамической памятью; создание и уничтожение динамических структур данных вынесено в библиотеку и производится обычно с помощьюфункций m allocQ , r e a llo c () и fre e Q .
В языке С и + + одновременнос выделением и освобождением памяти в некоторых случаях, а именно,при создании и удалении объекта типа структуры или класса, имеющегоконструкторы и/или деструкторы, необходимо выполнять дополнительные действия, заданные этими конструкторами и деструкторами.
Кроме того, при создании динамических объектов с помощью конструктораиного, нежели конструктор по умолчанию, необходима возможность указания параметров конструктора. Функции m allocQ и fre e Q ничего незнают о конструкторах, деструкторах и параметрах, поэтому для создания и удаления объектов они непригодны. Более того, информацией оконструкторах и деструкторах обладает только компилятор, поэтому вязыке Си-|—Ь вообще невозможно вынести работу с динамической памятью из языка в библиотеку без введения дополнительных средств.
Авторязыка С и + + Бьёрн Страуструп решил пойти более простым путем ивнёс в язык соответствующие синтаксические конструкции для созданияи удаления объектов.Для создания в динамической памяти одиночного объекта (переменной произвольного типа) в языке С и + + используется операция new, аргументом которой является имя типа создаваемого объекта. Например:in t * р ;р = new i n t ;Если создаваемый объект принадлежит типу, имеющему конструктор, ивозникает необходимость передать конструктору параметры, то эти параметры указываются в скобках после имени типа.
Так, создание объекта35описанного ранее типа Complex (см. § 2.1.5) с указанием действительнойи мнимой частей может выглядеть так:Complex * р ;р = new Complex(2 .4 , 7 .1 2 );Для удаления используется операция d elete:d e le te р;Для создания и удаления динамических массивов используются специальные век то р н ы е формы операций new и d elete , синтаксическиотличающиеся наличием квадратных скобок:in t *р = new i n t [200]; / / массив из 200 целых чиселd e le te [] р;/ / удаление массиваЭти формы отличаются тем, что соответствующий конструктор и деструктор вызываются для к аж д о го э л е м е н т а м ас си в а.Необходимо отметить, что векторная форма операции new не имеетсинтаксических средств для передачи параметров конструкторам, поэтому для создания массива элементов типа класс или структура необходим о наличие у э т о г о т и п а к о н с т р у к т о р а по умолчанию (см.§ 2.4).Важно знать, что объекты в динамической памяти, созданные с помощью векторной формы операции new, нельзя удалять с помощью обычной формы операции d elete и наоборот.
Дело в том, что реализацияменеджера динамической памяти вправе выделять память под обычныепеременные и под массивы из разных областей динамической памяти,имеющих, возможно, различную организацию служебных структур данных.Также не следует удалять с помощью d elete объекты, созданныефункцией m allocQ , и наоборот, не следует удалять обекты, созданныеоперациями new, с помощью fre e Q . Всё это может привести к непредсказуемым последствиям.§2.11.
К о н стр у к то р коп и рован и яРассмотрим следующую ситуацию. В реализации некоторого класса(назовем его C lsl) нам потребовался динамический массив, который мысоздаем в теле конструктора класса; естественно, в деструктор следуетпоместить оператор для уничтожения этого массива.c la s s C ls l {in t * р ;p u b lic :36C lslQ { p = new in t[2 0 ]; }~ C lsl() { d e le te [] p; }II . . .Теперь предположим, что кто-то создает в программе копию объектакласса C lsl. Такое может произойти, например, если объект окажетсяпередан по значению в качестве параметра функции.
Например:void f ( C l s l х) {II . . .}in t main() {и ...C lsl с;f (с) ;//...}Проанализируем происходящее со структурами данных при вызовефункции f (). Локальная переменная х является копией объекта с. Копиялюбого объекта данных создается путем обычного побитового копирования, если не указать иного. Следовательно, при копировании объектакласса C lsl скопирован окажется указатель на динамический массив;иначе говоря, у нас появятся два объекта, использующие один и тот жеэкземпляр динамического массива: оригинал объекта с и его локальнаякопия х.
Возникшая ситуация показана на рис. 2.1.Рис. 2.1. Схема структуры данных после побитового копирования объектаДаже если такое разделение не приведет к немедленным ошибкам(например, функция f () может изменить объект х, что отразится навнутреннем состоянии объекта с, чего мы могли и не ожидать), в любомслучае при выходе из функции f () отработает деструктор (для объектах), который уничтожит динамический массив, в результате чего объект сокажется в заведомо ошибочном состоянии, поскольку будет содержатьуказатель на уничтоженный массив.
К возникновению ошибки теперь37приведёт любое действие с объектом с, если же никаких действий непредпринимать, то ошибка возникнет при уничтожении объекта (когдадеструктор попытается вновь уничтожить уже уничтоженный массив).Очевидно, что побитовое копирование нас не устраивает и необходимопроинструктировать компилятор о том, каким именно способом можнокорректно скопировать объект класса C lsl. В языке С и + + для такихслучаев предусмотрен специальный случай конструктора, называемыйк о н с т р у к т о р о м копирования.Конструктор копирования — это конструктор, имеющий ровно одинпараметр, причем тип этого параметра представляет собой ссылку наобъект данного (описываемого) класса; в большинстве случаев эту ссылку снабжают модификатором const, чтобы показать, что при созданиикопии исходный объект не изменится.
Конструктор копирования представляет собой инструкцию компилятору относительно того, как скопировать объект данного типа, или, иначе говоря, как создать объектданного типа, уже имея один такой объект. Снабдим конструктором копирования наш класс C lsl:c la s s C ls l {in t * p ;p u b lic :C lslQ { p = new in t [20]; }C lsl(c o n st Clsl& a) {p = new i n t [20];f o r ( in t i= 0 ; i<20; i++) p [ i] = a . p [ i ] ;}~ C lsl() { d e le te [] p; }II ...>;Теперь ситуация при вызове функции f () будет выглядеть так, как показано на рис. 2.2.Рис.
2.2. Схема структуры данных после применения конструктора копирования38§2.12. В рем енны е и аноним ны е объектыС анонимными и временными объектами мы уже встречались ранее(см. §2.1.3, стр. 16; §2.5, стр. 27). Анонимные объекты обычно применяются в случае, когда объект создаётся, чтобы быть использованным лишьодин раз; как правило, в такой ситуации давать объекту имя не хочется,к тому же код с использованием анонимного объекта оказывается болеелаконичным и наглядным.