void suspicious(int s, int x) // плохой код
{
int* p = new int[s]; // занимаем память
vector
// ...
try {
if (x) p[x] = v.at(x);
// ...
} catch (...) { // перехватываем все исключения
delete[] p; // освобождаем память
throw; // генерируем исключение повторно
}
// ...
delete[] p; // освобождаем память
}
Этот код решает проблему за счет дополнительных инструкций и дублирования кода, освобождающего ресурсы (в данном случае инструкции delete[] p;
). Иначе говоря, это некрасивое решение; что еще хуже — его сложно обобщить. Представим, что мы задействовали несколько ресурсов.
void suspicious(vector
{
int* p = new int[s];
vector
// ...
int* q = new int[s];
vector
// ...
delete[] p;
delete[] q;
}
Обратите внимание на то, что, если оператор new
не сможет выделить свободную память, он сгенерирует стандартное исключение bad_alloc
. Прием try ... catc
h в этом примере также успешно работает, но нам потребуется несколько блоков try
, и код станет повторяющимся и ужасным. Мы не любим повторяющиеся и запутанные программы, потому что повторяющийся код сложно сопровождать, а запутанный код не только сложно сопровождать, но и вообще трудно понять.
ПОПРОБУЙТЕ
Добавьте блоки try
в последний пример и убедитесь, что все ресурсы будут правильно освобождаться при любых исключениях.
19.5.2. Получение ресурсов — это инициализация
К счастью, нам не обязательно копировать инструкции try...catch
, чтобы предотвратить утечку ресурсов. Рассмотрим следующий пример:
void f(vector
{
vector
vector
// ...
}
iostream
). Соответствующий принцип обычно формулируется довольно неуклюже: “Получение ресурса есть инициализация” (“Resource Acquisition Is Initialization” — RAII).
Рассмотрим предыдущий пример. Как только мы выйдем из функции f()
, будут вызваны деструкторы векторов p
и q
: поскольку переменные p
и q
не являются указателями, мы не можем присвоить им новые значения, инструкция return
не может предотвратить вызов деструкторов и никакие исключения не генерируются.
Это универсальное правило: когда поток управления покидает область видимости, вызываются деструкторы для каждого полностью созданного объекта и активизированного подобъекта. Объект считается полностью созданным, если его конструктор закончил свою работу. Исследование всех следствий, вытекающих из этих двух утверждений, может вызвать головную боль. Будем считать просто, что конструкторы и деструкторы вызываются, когда надо и где надо.
vector
, а не “голые” операторы new
и delete
.
19.5.3. Гарантии
Что делать, если вектор невозможно ограничить только одной областью (или подобластью) видимости? Рассмотрим пример.
vector
{
vector
// ...заполняем вектор данными;
// возможна генерация исключения...
return p;
}