Литмир - Электронная Библиотека
A
A

  // ...

  if (x) p = q;        // устанавливаем указатель p на другой объект

  // ...

  delete[] p;          // освобождаем память

}

Мы включили в программу инструкцию

if (x)
, чтобы гарантировать, что вы не будете знать заранее, изменилось ли значение указателя
p
или нет. Возможно, программа никогда не выполнит оператор
delete
.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  // ...

  if (x) return;

  // ...

  delete[] p; // освобождаем память

}

Возможно, программа никогда не выполнит оператор

delete
, потому что сгенерирует исключение.

void suspicious(int s, int x)

{

  int* p = new int[s]; // занимаем память

  vector<int> v;

  // ...

  if (x) p[x] = v.at(x);

  // ...

  delete[] p;          // освобождаем память

}

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Последняя возможность беспокоит нас больше всего. Когда люди впервые сталкиваются с такой проблемой, они считают, что она связана с исключениями, а не с управлением ресурсами. Не понимая истинных причин проблемы, они пытаются перехватывать исключения.

void suspicious(int s, int x) // плохой код

{

  int* p = new int[s]; // занимаем память

  vector<int> v;

  // ...

  try {

    if (x) p[x] = v.at(x);

    // ...

  } catch (...) {      // перехватываем все исключения

  delete[] p;          // освобождаем память

  throw;               // генерируем исключение повторно

  }

  // ...

  delete[] p;          // освобождаем память

}

Этот код решает проблему за счет дополнительных инструкций и дублирования кода, освобождающего ресурсы (в данном случае инструкции

delete[] p;
). Иначе говоря, это некрасивое решение; что еще хуже — его сложно обобщить. Представим, что мы задействовали несколько ресурсов.

void suspicious(vector<int>& v, int s)

{

  int* p = new int[s];

  vector<int>v1;

  // ...

  int* q = new int[s];

  vector<double> v2;

  // ...

  delete[] p;

  delete[] q;

}

Обратите внимание на то, что, если оператор

new
не сможет выделить свободную память, он сгенерирует стандартное исключение
bad_alloc
. Прием
try ... catc
h в этом примере также успешно работает, но нам потребуется несколько блоков
try
, и код станет повторяющимся и ужасным. Мы не любим повторяющиеся и запутанные программы, потому что повторяющийся код сложно сопровождать, а запутанный код не только сложно сопровождать, но и вообще трудно понять. 

ПОПРОБУЙТЕ

Добавьте блоки

try
в последний пример и убедитесь, что все ресурсы будут правильно освобождаться при любых исключениях.

19.5.2. Получение ресурсов — это инициализация

 К счастью, нам не обязательно копировать инструкции

try...catch
, чтобы предотвратить утечку ресурсов. Рассмотрим следующий пример:

void f(vector<int>& v, int s)

{

  vector<int> p(s);

  vector<int> q(s);

  // ...

}
 

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Это уже лучше. Что еще более важно, это очевидно лучше. Ресурс (в данном случае свободная память) занимается конструктором и освобождается соответствующим деструктором. Теперь мы действительно решили нашу конкретную задачу, связанную с исключениями. Это решение носит универсальный характер; его можно применить ко всем видам ресурсов: конструктор получает ресурсы для объекта, который ими управляет, а соответствующий деструктор их возвращает. Такой подход лучше всего зарекомендовал себя при работе с блокировками баз данных (database locks), сокетами (sockets) и буферами ввода-вывода (I/O buffers) (эту работу делают объекты класса
iostream
). Соответствующий принцип обычно формулируется довольно неуклюже: “Получение ресурса есть инициализация” (“Resource Acquisition Is Initialization” — RAII).

Рассмотрим предыдущий пример. Как только мы выйдем из функции

f()
, будут вызваны деструкторы векторов
p
и
q
: поскольку переменные
p
и
q
не являются указателями, мы не можем присвоить им новые значения, инструкция
return
не может предотвратить вызов деструкторов и никакие исключения не генерируются.

Это универсальное правило: когда поток управления покидает область видимости, вызываются деструкторы для каждого полностью созданного объекта и активизированного подобъекта. Объект считается полностью созданным, если его конструктор закончил свою работу. Исследование всех следствий, вытекающих из этих двух утверждений, может вызвать головную боль. Будем считать просто, что конструкторы и деструкторы вызываются, когда надо и где надо.

265
{"b":"847443","o":1}