Шрифт:
19.5.1. Потенциальные проблемы управления ресурсами
int* p = new int[s]; // занимаем память
Она заключается в трудности проверки того, что данному оператору new соответствует оператор
delete
. В функции suspicious
есть инструкция delete[] p
, которая могла бы освободить память, но представим себе несколько причин, по которым это может и не произойти. Какие инструкции можно было бы вставить в часть, отмеченную многоточием, ...
, чтобы вызвать утечку памяти? Примеры, которые мы подобрали для иллюстрации возникающих проблем, должны натолкнуть вас на размышления и вызвать подозрения относительно такого кода. Кроме того, благодаря этим примерам вы оцените простоту и мощь альтернативного решения. Возможно, указатель
p
больше не ссылается на объект, который мы хотим уничтожить с помощью оператора delete
.
void suspicious(int s, int x)
{
int* p = new int[s]; // занимаем память
// ...
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; // освобождаем память
}
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
, и код станет повторяющимся и ужасным. Мы не любим повторяющиеся и запутанные программы, потому что повторяющийся код сложно сопровождать, а запутанный код не только сложно сопровождать, но и вообще трудно понять.