Приобретение ресурса - это инициализация - Resource acquisition is initialization

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

RAII больше всего ассоциируется с C ++, откуда он возник, а также с D , Ada , Vala и Rust . Этот метод был разработан для безопасного управления ресурсами в C ++ в 1984–1989 годах, в основном Бьярном Страуструпом и Эндрю Кенигом , а сам термин был придуман Страуструпом. RAII обычно произносится как инициализм , иногда произносится как «R, A, double I».

Другие названия этой идиомы включают Constructor Acquires, Destructor Releases (CADRe), а один конкретный стиль использования называется Scope-based Resource Management (SBRM). Последний термин относится к частному случаю автоматических переменных . RAII связывает ресурсы со временем жизни объекта , которое может не совпадать с входом и выходом из области видимости. (Примечательно, что переменные, размещенные в бесплатном хранилище, имеют время жизни, не связанное с какой-либо заданной областью действия.) Однако использование RAII для автоматических переменных (SBRM) является наиболее распространенным вариантом использования.

Пример C ++ 11

В следующем примере C ++ 11 демонстрируется использование RAII для доступа к файлам и блокировки мьютексов :

#include <fstream>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <string>

void WriteToFile(const std::string& message) {
  // |mutex| is to protect access to |file| (which is shared across threads).
  static std::mutex mutex;

  // Lock |mutex| before accessing |file|.
  std::lock_guard<std::mutex> lock(mutex);

  // Try to open file.
  std::ofstream file("example.txt");
  if (!file.is_open()) {
    throw std::runtime_error("unable to open file");
  }

  // Write |message| to |file|.
  file << message << std::endl;

  // |file| will be closed first when leaving scope (regardless of exception)
  // |mutex| will be unlocked second (from lock destructor) when leaving scope
  // (regardless of exception).
}

Этот код безопасен в отношении исключений, поскольку C ++ гарантирует, что все объекты стека будут уничтожены в конце охватывающей области, известной как раскрутка стека . Таким образом, деструкторы как объекта блокировки, так и объекта файла гарантированно будут вызываться при возврате из функции, независимо от того, было создано исключение или нет.

Локальные переменные позволяют легко управлять несколькими ресурсами в рамках одной функции: они уничтожаются в порядке, обратном их построению, и объект уничтожается только в том случае, если он полностью построен, то есть если из его конструктора не распространяется исключение.

Использование RAII значительно упрощает управление ресурсами, уменьшает общий размер кода и помогает обеспечить правильность программы. Поэтому RAII рекомендуется в соответствии с отраслевыми стандартами, и большая часть стандартной библиотеки C ++ следует этой идиоме.

Преимущества

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

Инкапсуляция обеспечивается потому, что логика управления ресурсами определяется один раз в классе, а не на каждом сайте вызова. Безопасность исключений обеспечивается для ресурсов стека (ресурсов, которые высвобождаются в той же области, в которой они были получены), путем привязки ресурса к времени жизни переменной стека (локальной переменной, объявленной в данной области): если генерируется исключение , и имеется надлежащая обработка исключений, единственный код, который будет выполняться при выходе из текущей области, - это деструкторы объектов, объявленных в этой области. Наконец, локальность определения обеспечивается записью определений конструктора и деструктора рядом друг с другом в определении класса.

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

Сравнивая RAII с finallyконструкцией, используемой в Java, Страуструп писал, что «В реалистичных системах ресурсов гораздо больше, чем видов ресурсов, поэтому метод« получение ресурсов - это инициализация »приводит к меньшему количеству кода, чем использование конструкции« finally ». ”

Типичное использование

Дизайн RAII часто используется для управления блокировками мьютексов в многопоточных приложениях. При таком использовании объект снимает блокировку при разрушении. Без RAII в этом сценарии вероятность тупиковой ситуации была бы высока, и логика блокировки мьютекса была бы далека от логики его разблокировки. При использовании RAII код, который блокирует мьютекс, по существу включает логику, согласно которой блокировка будет снята, когда выполнение выйдет за пределы области действия объекта RAII.

Другой типичный пример - взаимодействие с файлами: у нас может быть объект, представляющий файл, открытый для записи, при этом файл открывается в конструкторе и закрывается, когда выполнение выходит за пределы области действия объекта. В обоих случаях RAII гарантирует только то, что соответствующий ресурс высвобождается надлежащим образом; по-прежнему необходимо соблюдать осторожность, чтобы обеспечить безопасность исключений. Если код, изменяющий структуру данных или файл, не является безопасным для исключений, мьютекс может быть разблокирован или файл закрыт со структурой данных или файлом поврежден.

Владение динамически выделяемыми объектами (память, выделенная с помощью newC ++) также можно контролировать с помощью RAII, так что объект освобождается при уничтожении объекта RAII (основанного на стеке). С этой целью стандартная библиотека C ++ 11 определяет классы интеллектуальных указателей для объектов с std::unique_ptrодним владельцем и std::shared_ptrдля объектов с общим владением. Подобные классы также доступны std::auto_ptrв C ++ 98 и boost::shared_ptrв библиотеках Boost .

Расширения "очистки" компилятора

И Clang, и коллекция компиляторов GNU реализуют нестандартное расширение языка C для поддержки RAII: атрибут переменной "cleanup". Следующий макрос аннотирует переменную заданной функцией деструктора, которую он будет вызывать, когда переменная выходит за пределы области видимости:

static inline void fclosep(FILE **fp) { if (*fp) fclose(*fp); }
#define _cleanup_fclose_ __attribute__((cleanup(fclosep)))

Затем этот макрос можно использовать следующим образом:

void example_usage() {
  _cleanup_fclose_ FILE *logfile = fopen("logfile.txt", "w+");
  fputs("hello logfile!", logfile);
}

В этом примере компилятор организует вызов функции fclosep в файле журнала перед возвратом example_usage .

Ограничения

RAII работает только для ресурсов , приобретенных и выпущенных (прямо или косвенно) в стеке выделяются объектами, где является вполне определенным статическим сроком службы объекта. Объекты, размещенные в куче, которые сами получают и высвобождают ресурсы, распространены во многих языках, включая C ++. RAII зависит от объектов на основе кучи, которые должны быть неявно или явно удалены на всех возможных путях выполнения, чтобы запустить его деструктор, высвобождающий ресурсы (или эквивалент). Этого можно достичь, используя интеллектуальные указатели для управления всеми объектами кучи со слабыми указателями для объектов с циклическими ссылками.

В C ++ разворачивание стека гарантировано только в том случае, если где-то будет обнаружено исключение. Это связано с тем, что «Если в программе не найден соответствующий обработчик, вызывается функция terminate (); независимо от того, раскручивается ли стек перед этим вызовом terminate (), определяется реализацией (15.5.1)». (Стандарт C ++ 03, §15.3 / 9). Такое поведение обычно приемлемо, поскольку операционная система освобождает оставшиеся ресурсы, такие как память, файлы, сокеты и т. Д., При завершении программы.

Подсчет ссылок

Perl , Python (в реализации CPython ) и PHP управляют временем жизни объекта путем подсчета ссылок , что позволяет использовать RAII. Объекты, на которые больше не ссылаются, немедленно уничтожаются или финализируются и освобождаются, поэтому деструктор или финализатор могут освободить ресурс в это время. Однако в таких языках это не всегда идиоматично, и в Python это особо не рекомендуется (в пользу диспетчеров контекста и финализаторов из пакета weakref ).

Однако время жизни объекта не обязательно привязано к какой-либо области, и объекты могут быть уничтожены недетерминированно или вообще не уничтожены. Это делает возможной случайную утечку ресурсов, которые должны были быть освобождены в конце некоторой области. Объекты, хранящиеся в статической переменной (в частности, в глобальной переменной ), не могут быть финализированы при завершении программы, поэтому их ресурсы не высвобождаются; CPython, например, не гарантирует финализации таких объектов. Кроме того, объекты с циклическими ссылками не будут собираться простым счетчиком ссылок и будут жить неопределенно долго; даже если они собраны (более сложной сборкой мусора), время уничтожения и порядок уничтожения будут недетерминированными. В CPython есть детектор циклов, который обнаруживает циклы и завершает объекты в цикле, хотя до CPython 3.4 циклы не собираются, если какой-либо объект в цикле имеет финализатор.

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

дальнейшее чтение

внешние ссылки