Применение аппарата исключений очень полезно при написании устойчивого кода, потребляющего в своей работе различные ресурсы (динамическая память, файлы и т.п.). В случае возникновения критической ситуации и невозможности дальнейшего продолжения работы устойчивый код должен корректно освободить занимаемые им ресурсы.
Рассмотрим пример фрагмента кода, который захватывает ресурсы и обращается к критической функции void critical (char*, int):
int numread; char* buffer = NULL; // инициализация для детектирования возможных измененийint fd = -1; // ... аналогичноtry { buffer = newchar [256]; fd = open ( "data", "r" ); // открываем файл и читаем по 256 символовwhile ( 0 < ( numread = read ( fd, buffer, 256 ) ) ) { critical ( buffer, numread ); // вызов критической функции } } catch ( ... ) { if ( 0 < fd ) close ( fd ); // освобождение ресурсовif ( buffer ) delete buffer; // ... аналогичноthrow; // делегирование исключения внешнему обработчику } if ( 0 < fd ) close ( fd ); // освобождение ресурсовif ( buffer ) delete buffer; // ... аналогично |
В этом примере видно, что операции по освобождению ресурсов повторяются два раза: при нормальном ходе выполнения и при возникновении критической ситуации. В некоторые языки программирования для такого случая была введена конструкция вида try-finally, в которой секция finally выполнялась обязательно, как при возникновении исключений, так и при нормальном ходе выполнения программы. Поскольку в языке C++ такой конструкции явно не присутствует, мы можем попытаться описать её самостоятельно, прибегая к помощи макропрепроцессора:
#define finally(Saver) \ catch ( ... ) \ { \ Saver \ throw; \ } \ Saver |
Тогда вышеприведенный код можно будет записать следующим образом:
char* buffer = NULL; int fd = -1, numread; try { buffer = newchar [256]; fd = open ( "data", "r" ); // открываем файл и читаем по 256 символовwhile ( 0 < ( numread = read ( fd, buffer, 256 ) ) ) { critical ( buffer, numread ); // вызов критической функции } } finally ( // код освобождения ресурсов помещён в круглые скобки, так какif ( 0 < fd ) close ( fd ); // он является текстовым параметром макросаif ( buffer ) delete buffer; ) |
Как видно из текста макроса, фактическое дублирование кода было переложено на макрогенератор. Следует отметить, что макрос finally «прозрачен» для исключений, поэтому его использование аналогично конструкциям try-finally других языков. Среди достоинств такого подхода можно отметить повышение наглядности кода, уменьшение ошибок вида copy-paste, а к недостаткам следует отнести невозможность пошаговой отладки кода finally и ограничение на использование запятых, которые по синтаксису являются разделителями параметров макроса.