Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

// для других n чисел типа double)

// ...используем p и v...

delete[ ] p; // освобождаем память, занятую массивом

// чисел типа double

} // класс vector автоматически освободит

// память, занятую объектом v

Оказывается, оператор

delete[]
такой скучный и подвержен ошибкам! Имея класс
vector
, нет необходимости ни выделять память с помощью оператора
new
, ни освобождать ее с помощью оператора
delete[]
при выходе из функции. Все это намного лучше сделает класс
vector
. В частности, класс
vector
никогда не забудет вызвать деструктор, чтобы освободить память, занятую его элементами.

Здесь мы не собираемся глубоко вдаваться в детали использования деструкторов. Отметим лишь, что они играют очень важную роль при работе с ресурсами, которые сначала резервируются, а потом возвращаются обратно файлами, потоками, блокировками и т.д. Помните, как очищались потоки
iostream
? Они очищали буферы, закрывали файлы, освобождали память и т.д. Все это делали их деструкторы. Каждый класс, “владеющий” какими-то ресурсами, должен иметь деструктор.

17.5.1. Обобщенные указатели

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

struct Customer {

string name;

vector<string> addresses;

// ...

};

void some_fct

{

Customer fred;

// инициализация объекта fred

// использование объекта fred

}

Когда мы выйдем из функции

some_fct
и объект
fred
покинет свою область видимости, он будет уничтожен; иначе говоря, будут вызваны деструкторы для строки name и вектора
addresses
. Это совершенно необходимо, поскольку иначе могут возникнуть проблемы. Иногда это выражают таким образом: компилятор сгенерировал деструктор для класса
Customer
, который вызывает деструкторы членов. Такая генерация выполняется компилятором часто и позволяет гарантированно вызывать деструкторы членов класса.

Деструкторы для членов — и для базовых классов — неявно вызываются из деструктора производного класса (либо определенного пользователем, либо сгенерированного). По существу, все правила сводятся к одному: деструкторы вызываются тогда, когда объект уничтожается (при выходе из области видимости, при выполнении оператора

delete
и т.д.).

17.5.2. Деструкторы и свободная память

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

• Если функции в качестве ресурса требуется какой-то объект, она обращается к конструктору.

• На протяжении своего срока существования объект может освобождать ресурсы и запрашивать новые.

• В конце существования объекта деструктор освобождает все ресурсы, которыми владел объект.

Типичным примером является пара “конструктор–деструктор” в классе

vector
, которая управляет свободной памятью. Мы еще вернемся к этой теме в разделе 19.5. А пока рассмотрим важное сочетание механизма управления свободной памятью и иерархии классов.

Shape* fct

{

Text tt(Point(200,200),"Annemarie");

// ...

Shape* p = new Text(Point(100,100),"Nicholas");

return p;

}

void f

{

Shape* q = fct;

// ...

delete q;

}

Этот

код выглядит логичным — и он действительно логичен. Все работает, но посмотрите, как именно работает, ведь этот код является примером элегантного, важного и простого метода. При выходе из функции
fct
объект
tt
класса
Text
(см. раздел 3.11), существующий в ней, уничтожается вполне корректно. Класс
Text
имеет член типа
string
, у которого обязательно нужно вызвать деструктор, — класс
string
занимает и освобождает память примерно так же, как и класс
vector
. Для объекта
tt
это просто; компилятор вызывает сгенерированный деструктор класса
Text
, как описано в разделе 17.5.1. А что можно сказать об объекте класса
Text
возвращаемом функцией
fct
? Вызывающая функция
f
понятия не имеет о том, что указатель
q
ссылается на объект класса
Text
; ей известно лишь, что он ссылается на объект класса
Shape
. Как же инструкция
delete q
сможет вызвать деструктор класса
Text
?

В разделе 14.2.1 мы вскользь упомянули о том, что класс

Shape
имеет деструктор. Фактически в классе
Shape
есть виртуальный деструктор. В этом все дело. Когда мы выполняем инструкцию
delete q
, оператор
delete
анализирует тип указателя
q
, чтобы увидеть, нужно ли вызывать деструктор, и при необходимости он его вызывает. Итак, инструкция
delete q
вызывает деструктор
~Shape
класса
Shape
. Однако деструктор
~Shape
является виртуальным, поэтому с помощью механизма вызова виртуальной функции (см. раздел 17.3.1) он вызывает деструктор класса, производного от класса
Shape
, в данном случае деструктор
~Text
. Если бы деструктор
Shape::~Shape
не был виртуальным, то деструктор
Text::~Text
не был бы вызван и член класса
Text
, имеющий тип
string
, не был бы правильно уничтожен.

Запомните правило: если класс содержит виртуальную функцию, в нем должен быть виртуальный деструктор. Причины заключаются в следующем.

1. Если класс имеет виртуальную функцию, то, скорее всего, он будет использован в качестве базового.

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

new
.

3. Если объект производного класса размещается в памяти с помощью оператора

new
, а работа с ним осуществляется с помощью указателя на базовый класс, то, скорее всего, он будет удален с помощью обращения к указателю на объект базового класса.

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

delete
. Они никогда не вызываются непосредственно. Это позволяет избежать довольно трудоемкой работы.

ПОПРОБУЙТЕ

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

17.6. Доступ к элементам

Для того чтобы нам было удобно работать с классом

vector
, нужно читать и записывать элементы. Для начала рассмотрим простые функции-члены
get
и
set
.

// очень упрощенный вектор чисел типа double

class vector {

int sz; // размер

double* elem; // указатель на элементы

public:

Поделиться с друзьями: