Шрифт:
Интервал:
Закладка:
for (int i=0; i<s; ++i) elem[i]=0; // инициализация
// элементов
}
int size() const { return sz; } // текущий размер
// ...
};
Итак, член sz хранит количество элементов. Мы инициализируем его в конструкторе, а пользователь класса vector может выяснить количество элементов, вызвав функцию size(). Память для элементов выделяется в конструкторе с помощью оператора new, а указатель, возвращенный оператором new, хранится в члене elem.
Обратите внимание на то, что мы инициализируем элементы их значением по умолчанию (0.0). Класс vector из стандартной библиотеки делает именно так, поэтому мы решили сделать так же с самого начала.
К сожалению, наш примитивный класс vector допускает утечку памяти. В конструкторе он выделяет память для элементов с помощью оператора new. Следуя правилу, сформулированному в разделе 17.4, мы должны освободить эту память с помощью оператора delete. Рассмотрим пример.
void f(int n)
{
vector v(n); // выделяем память для n чисел типа double
// ...
}
После выхода из функции f() элементы вектора v, созданные в свободной памяти, не удаляются. Мы могли бы определить функцию clean_up() — член класса vector и вызвать ее следующим образом:
void f2(int n)
{
vector v(n); // определяем вектор,
// выделяющий память для других n переменных
// типа int
// ...используем вектор v...
v.clean_up(); // функция clean_up() удаляет член elem
}

// очень упрощенный вектор, содержащий числа типа double
class vector {
int sz; // размер
double* elem; // указатель на элементы
public:
vector(int s) // конструктор
:sz(s), elem(new double[s]) // выделяет память
{
for (int i=0; i<s; ++i) elem[i]=0; // инициализируем
// элементы
}
~vector() // деструктор
{ delete[] elem; } // освобождаем память
// ...
};
Итак, теперь можно написать следующий код:
void f3(int n)
{
double* p = new double[n]; // выделяем память для n
// чисел типа double
vector v(n); // определяем вектор (выделяем память
// для других n чисел типа double)
// ...используем p и v...
delete[ ] p; // освобождаем память, занятую массивом
// чисел типа double
} // класс vector автоматически освободит
// память, занятую объектом v
Оказывается, оператор delete[] такой скучный и подвержен ошибкам! Имея класс vector, нет необходимости ни выделять память с помощью оператора new, ни освобождать ее с помощью оператора delete[] при выходе из функции. Все это намного лучше сделает класс vector. В частности, класс vector никогда не забудет вызвать деструктор, чтобы освободить память, занятую его элементами.

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;
}