Симуляция блока try-finally в С++ с помощью макросов

читайте также по теме: Вариант на RSDN

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

Контроль освобождения ресурсов

Рассмотрим пример фрагмента кода, который захватывает ресурсы и обращается к критической функции.

void critical ( char*, int );

int   numread;
char* buffer (NULL); // инициализация для детектирования возможных изменений
int   fd     (-1);   // ... аналогично
try
{
    buffer = new char[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 = new char [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;
)

К недостаткам подобного решения, не позволяющим применять его в промышленных масштабах, следует отнести:

Более корректным с точки зрения языка способом является использование «стражей», специальных классов, которые следят за жизненным циклом объектов.

19 марта 2004—15 февраля 2005
Максим Проскурня
1997–2024 Axofiber, axofiber.info