Используйте RAII для предотвращения утечек
Причина
Утечки обычно неприемлемы. Мануальное освобождение ресурсов подвержено ошибкам. RAII ("Resource Acquisition Is Initialization" — инициализация ресурса является его получением) — это самый простой и систематичный способ предотвращения утечек.
Пример
void f1(int i) // Плохо: возможна утечка
{
int* p = new int[12];
// ...
if (i < 17) throw Bad{"in f()", i};
// ...
}
Мы могли бы аккуратно освободить ресурс перед throw:
void f2(int i) // Неуклюже и подвержено ошибкам: явное освобождение
{
int* p = new int[12];
// ...
if (i < 17) {
delete[] p;
throw Bad{"in f()", i};
}
// ...
}
Это многословно. В более крупном коде с несколькими возможными throwами явное освобождение становится повторяющимся и подверженным ошибкам.
void f3(int i) // ОК: управление ресурсами выполняется дескриптором (но см. ниже)
{
auto p = make_unique<int[]>(12);
// ...
if (i < 17) throw Bad{"in f()", i};
// ...
}
Обратите внимание, что это работает даже когда throw неявен, поскольку он произошёл в вызванной функции:
void f4(int i) // ОК: управление ресурсами выполняется дескриптором (но см. ниже)
{
auto p = make_unique<int[]>(12);
// ...
helper(i); // может выбросить исключение
// ...
}
Если вам действительно не нужна семантика указателя, используйте локальный объект-ресурс:
void f5(int i) // ОК: управление ресурсами выполняется локальным объектом
{
vector<int> v(12);
// ...
helper(i); // может выбросить исключение
// ...
}
Это ещё проще и безопаснее, и часто более эффективно.
Примечание
Если нет очевидного дескриптора ресурса и по какой-то причине определение правильного объекта/дескриптора RAII невозможно, в качестве последнего средства действия очистки могут быть представлены объектом final_action.
Примечание
Но что мы делаем, если пишем программу, в которой исключения не могут быть использованы? Сначала оспорьте это предположение; вокруг исключений циркулирует много мифов. Мы знаем только несколько хороших причин:
(в частности без узнаваемой стратегии владения), так что исключения могли бы вызвать утечки.
(медленная, потребляющая память, не работающая корректно для динамически связанных библиотек и т.д.). Пожалуйтесь поставщику вашей реализации; если никто не жалуется, улучшений не будет.
- Мы находимся в системе такого размера, что поддержка исключений съела бы большую часть нашей памяти объёмом 2K.
- Мы находимся в жёсткой системе реального времени и у нас нет инструментов, которые гарантируют нам, что исключение обрабатывается в требуемое время.
- Мы находимся в системе с тоннами старого кода, использующего много указателей в трудных для понимания способами
- Наша реализация механизмов исключений C++ неразумно плохая
- Нас уволят, если мы оспорим древнюю мудрость нашего менеджера.
Только первая из этих причин является фундаментальной, поэтому всякий раз, когда это возможно, используйте исключения для реализации RAII или проектируйте объекты RAII так, чтобы они никогда не терпели неудачу. Когда исключения не могут быть использованы, имитируйте RAII. То есть систематически проверяйте, что объекты действительны после конструирования и по-прежнему освобождают все ресурсы в деструкторе. Одна из стратегий — добавить операцию valid() к каждому дескриптору ресурса:
void f()
{
vector<string> vs(100); // не std::vector: добавлена valid()
if (!vs.valid()) {
// обработать ошибку или выход
}
ifstream fs("foo"); // не std::ifstream: добавлена valid()
if (!fs.valid()) {
// обработать ошибку или выход
}
// ...
} // деструкторы выполняют очистку как обычно
Очевидно, это увеличивает размер кода, не позволяет неявное распространение «исключений» (проверки valid()), и проверки valid() можно забыть. Предпочитайте использование исключений.
См. также: Использование noexcept
Применение
???