Бьерн Страуструп - Язык программирования С++. Главы 9-10
Страница 9. Запросы ресурсов



9.4 Запросы ресурсов

 Если в некоторой функции потребуются определенные ресурсы, например,
 нужно открыть файл, отвести блок памяти в области свободной
 памяти, установить монопольные права доступа и т.д., для дальнейшей
 работы системы обычно бывает крайне важно, чтобы ресурсы были
 освобождены надлежащим образом. Обычно такой "надлежащий способ"
 реализует функция, в которой происходит запрос ресурсов и освобождение
 их перед выходом. Например:

            void use_file(const char* fn)
            {
              FILE* f = fopen(fn,"w");

              // работаем с f

              fclose(f);
            }

 Все это выглядит вполне нормально до тех пор, пока вы не поймете,
 что при любой ошибке, происшедшей после вызова fopen() и до вызова
 fclose(), возникнет особая ситуация, в результате которой мы
 выйдем из use_file(), не обращаясь к fclose().Ь

 Ь Стоит сказать, что та же проблема возникает и в языках, не
 поддерживающих особые ситуации. Так, обращение к функции longjump()
 из стандартной библиотеки С может иметь такие же неприятные
 последствия.

 Если вы создаете устойчивую к ошибкам системам, эту проблему
 придется решать. Можно дать примитивное решение:

            void use_file(const char* fn)
            {
              FILE* f = fopen(fn,"w");
              try {
                  // работаем с f
              }
              catch (...) {
                  fclose(f);
                  throw;
              }
              fclose(f);
            }

 Вся часть функции, работающая с файлом f, помещена в проверяемый
 блок, в котором перехватываются все особые ситуации, закрывается
 файл и особая ситуация запускается повторно.
     Недостаток этого решения в его многословности, громоздкости
 и потенциальной расточительности. К тому же всякое многословное
 и громоздкое решение чревато ошибками, хотя бы в силу усталости
 программиста. К счастью, есть более приемлемое решение. В общем
 виде проблему можно сформулировать так:

           void acquire()
           {
             // запрос ресурса 1
             // ...
             // запрос ресурса n

             // использование ресурсов

             // освобождение ресурса n
             // ...
             // освобождение ресурса 1
           }

 Как правило бывает важно, чтобы ресурсы освобождались в обратном
 по сравнению с запросами порядке. Это очень сильно напоминает порядок
 работы с локальными объектами, создаваемыми конструкторами и
 уничтожаемыми деструкторами. Поэтому мы можем решить проблему запроса и
 освобождения ресурсов, если будем использовать подходящие объекты
 классов с конструкторами и деструкторами. Например, можно определить
 класс FilePtr, который выступает как тип FILE* :

           class FilePtr {
              FILE* p;
           public:
              FilePtr(const char* n, const char* a)
                { p = fopen(n,a); }
              FilePtr(FILE* pp) { p = pp; }
              ~FilePtr() { fclose(p); }

              operator FILE*() { return p; }
           };

 Построить объект FilePtr можно либо, имея объект типа FILE*, либо,
 получив нужные для fopen() параметры. В любом случае этот
 объект будет уничтожен при выходе из его области видимости, и
 его деструктор закроет файл. Теперь наш пример сжимается до такой
 функции:

           void use_file(const char* fn)
           {
             FilePtr f(fn,"w");
             // работаем с f
           }

 Деструктор будет вызываться независимо от того, закончилась ли функция
 нормально, или произошел запуск особой ситуации.

 
« Предыдущая статья   Следующая статья »