Получение ресурсов — это инициализация

Подход к управлению ресурсами путем привязки их к времени жизни объекта

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

RAII ассоциируется в первую очередь с C++ , где он и возник, а также с Ada , [3] Vala , [4] и Rust . [5] Метод был разработан для безопасного управления ресурсами в C++ [6] в 1984–1989 годах, в первую очередь Бьярном Страуструпом и Эндрю Кёнигом , [7] а сам термин был придуман Страуструпом. [8]

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

Пример С++11

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

#include <fstream> #include <iostream> #include <mutex> #include <stdexcept> #include <string>     void WriteToFile ( const std :: string & message ) { // |mutex| предназначен для защиты доступа к |файлу| (который является общим для потоков). static std :: mutex mutex ;         // Блокируем |мьютекс| перед доступом к |файлу|. std :: lock_guard < std :: mutex > lock ( mutex );   // Попробовать открыть файл. std :: ofstream file ( "example.txt" ); if ( ! file.is_open ( ) ) { throw std :: runtime_error ( "unable to open file" ); }         // Записать |сообщение| в |файл|. file << message << std :: endl ;      // |file| будет закрыт первым при выходе из области видимости (независимо от исключения) // |mutex| будет разблокирован вторым (из деструктора |lock|) при выходе из области видимости // (независимо от исключения). }  

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

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

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

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

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

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

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

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

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

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

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

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

Также сообщения могут быть отправлены на сетевые ресурсы с использованием RAII. В этом случае объект RAII отправит сообщение в сокет в конце конструктора, когда его инициализация завершена. Он также отправит сообщение в начале деструктора, когда объект вот-вот будет уничтожен. Такая конструкция может использоваться в клиентском объекте для установления соединения с сервером, запущенным в другом процессе.

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

И Clang , и GNU Compiler Collection реализуют нестандартное расширение языка C для поддержки RAII: атрибут переменной «cleanup». [16] Следующий код аннотирует переменную с заданной функцией деструктора, которая будет вызвана, когда переменная выйдет из области видимости:

void example_usage () { __attribute__ (( cleanup ( fclosep ))) ФАЙЛ * logfile = fopen ( "logfile.txt" , "w+" ); fputs ( "привет logfile!" , logfile ); }          

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

Ограничения

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

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

На конференции Gamelab 2018 года Джонатан Блоу заявил, что использование RAII может привести к фрагментации памяти , что в свою очередь может привести к промахам кэша и снижению производительности в 100 и более раз . [19]

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

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

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

Ссылки

  1. ^ ab Stroustrup, Bjarne (2017-09-30). "Почему C++ не предоставляет конструкцию "finally"?" . Получено 2019-03-09 .
  2. ^ Sutter, Herb ; Alexandrescu, Andrei (2005). C++ Coding Standards . C++ In-Depth Series. Addison-Wesley. стр. 24. ISBN 978-0-321-11358-0.
  3. ^ "Gem #70: The Scope Locks Idiom". AdaCore . Получено 21 мая 2021 г. .
  4. ^ Проект Valadate. "Destruction". Учебник Vala версии 0.30 . Получено 21 мая 2021 г.
  5. ^ "RAII - Rust By Example". doc.rust-lang.org . Получено 22.11.2020 .
  6. ^ Страуструп 1994, 16.5 Управление ресурсами, стр. 388–89.
  7. ^ Страуструп 1994, 16.1 Обработка исключений: Введение, стр. 383–84.
  8. ^ Страуструп 1994, стр. 389. Я назвал этот метод «приобретение ресурсов — это инициализация».
  9. ^ Артур Чайковский (2012-11-06). "Изменить официальный RAII на CADRe". Стандарт ISO C++ - Будущие предложения . Группы Google . Получено 2019-03-09 .
  10. ^ Чоу, Аллен (1 октября 2014 г.). «Управление ресурсами на основе объема (RAII)» . Проверено 9 марта 2019 г.
  11. ^ Ричард Смит (21.03.2017). "Рабочий проект, стандарт языка программирования C++" (PDF) . стр. 151, раздел §9.6 . Получено 07.09.2023 .
  12. ^ «Как мне справиться с деструктором, который дает сбой?». Standard C++ Foundation . Получено 09.03.2019 .
  13. ^ Ричард Смит (21.03.2017). "Рабочий проект, стандарт языка программирования C++" (PDF) . Получено 09.03.2019 .
  14. ^ Страуструп, Бьярн ; Саттер, Херб (3 августа 2020 г.). «Основные рекомендации по C++» . Проверено 15 августа 2020 г.
  15. ^ «У меня слишком много блоков try; что с этим делать?». Standard C++ Foundation . Получено 09.03.2019 .
  16. ^ "Указание атрибутов переменных". Использование коллекции компиляторов GNU (GCC) . Проект GNU . Получено 2019-03-09 .
  17. ^ Weimer, Westley; Necula, George C. (2008). «Исключительные ситуации и надежность программ» (PDF) . ACM Transactions on Programming Languages ​​and Systems . Vol. 30, no. 2.
  18. ^ ildjarn (2011-04-05). "RAII и раскручивание стека". Stack Overflow . Получено 2019-03-09 .
  19. ^ Gamelab2018 - Решения Джона Блоу по дизайну создания Jai, нового языка для программистов игр на YouTube
  20. ^ "Расширение Python с помощью C или C++: количество ссылок". Расширение и встраивание интерпретатора Python . Python Software Foundation . Получено 09.03.2019 .
  21. ^ hobbs (2011-02-08). "Поддерживает ли PHP шаблон RAII? Как?" . Получено 2019-03-09 .
  22. ^ "gc — Интерфейс сборщика мусора". Стандартная библиотека Python . Python Software Foundation . Получено 2019-03-09 .

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

  • Пример главы: «Поиск № 67: Неспособность использовать приобретение ресурсов — это инициализация» Стивена К. Дьюхерста
  • Интервью: «Разговор с Бьерном Страуструпом» Билла Веннерса
  • Статья: «Закон большой двойки» Бьорна Карлссона и Мэтью Уилсона
  • Статья: «Реализация идиомы «Приобретение ресурсов — это инициализация»» Дэнни Калева
  • Статья: Роланд Пибингер «RAII, динамические объекты и фабрики в C++».
  • RAII в Delphi: «Однострочный RAII в Delphi» Барри Келли
  • Руководство: RAII в C++ от W3computing
Получено с "https://en.wikipedia.org/w/index.php?title=Приобретение_ресурсов_является_инициализацией&oldid=1269103709"