Читаем Программирование полностью

  Этот подход должен был бы сработать. Однако одна из наиболее распространенных проблем, связанных со свободной памятью, заключается в том, что люди забывают об операторе delete. Эквивалентная проблема может возникнуть и с функцией clean_up(); люди просто забудут ее вызвать. Мы можем предложить более удачное решение. Основная идея состоит в том, чтобы компилятор знал не только о конструкторе, но и о функции, играющей роль, противоположную конструктору. Такую функцию логично назвать деструктором (destructor). Точно так же как конструктор неявно вызывается при создании объекта класса, деструктор неявно вызывается, когда объект выходит за пределы области видимости. Конструктор гарантирует, что объект будет правильно создан и проинициализирован. Деструктор, наоборот, гарантирует, что объект будет правильно очищен перед тем, как будет уничтожен. Рассмотрим пример.

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

class vector {

  int sz; // размер

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

public:

  vector(int s) // конструктор

  :sz(s), elem(new double[s]) // выделяет память

  {

    for (int i=0; i

  // элементы

  }

  ~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 никогда не забудет вызвать деструктор, чтобы освободить память, занятую его элементами.

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

<p id="AutBody_Root307"><strong>17.5.1. Обобщенные указатели</strong></p>

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

struct Customer {

  string name;

  vector addresses;

  // ...

};

void some_fct()

{

  Customer fred;

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

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

}

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

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

<p id="AutBody_Root308"><strong>17.5.2. Деструкторы и свободная память</strong></p>

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

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

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

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже