stringtranslate.com

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

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

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

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

пример С++11

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

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

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

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

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

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

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

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

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

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

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

Конструкция 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 .

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

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

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

void example_usage () { __attribute__ (( очистка ( fclosep ))) FILE * logfile = fopen ( "logfile.txt" , "w+" ); fputs ( «привет, файл журнала!» , файл журнала ); }          

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

Ограничения

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

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

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

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

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

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

Рекомендации

  1. ^ аб Страуструп, Бьярн (30 сентября 2017 г.). «Почему C++ не предоставляет конструкцию «наконец»?» . Проверено 9 марта 2019 г.
  2. ^ Саттер, Херб ; Александреску, Андрей (2005). Стандарты кодирования C++ . Серия углубленного изучения C++. Аддисон-Уэсли. п. 24. ISBN 978-0-321-11358-0.
  3. ^ "Жемчужина № 70: Идиома блокировки прицела" . АдаКор . Проверено 21 мая 2021 г.
  4. ^ Проект Валадате. «Разрушение». Учебное пособие по Vala версии 0.30 . Проверено 21 мая 2021 г.
  5. ^ "RAII - Ржавчина на примере" . doc.rust-lang.org . Проверено 22 ноября 2020 г.
  6. ^ Страуструп 1994, 16.5 Управление ресурсами, стр. 388–89.
  7. ^ Страуструп 1994, 16.1 Обработка исключений: Введение, стр. 383–84.
  8. ^ Страуструп 1994, с. 389. Я назвал эту технику «приобретение ресурсов — это инициализация».
  9. ^ Артур Чайковский (06 ноября 2012 г.). «Изменить официальный RAII на CADRe». Стандарт ISO C++ — будущие предложения . Группы Google . Проверено 9 марта 2019 г.
  10. ^ Чоу, Аллен (01 октября 2014 г.). «Управление ресурсами на основе объема (RAII)» . Проверено 9 марта 2019 г.
  11. ^ Ричард Смит (21 марта 2017 г.). «Рабочий проект стандарта языка программирования C++» (PDF) . п. 151, раздел §9.6 . Проверено 7 сентября 2023 г.
  12. ^ «Как мне справиться с неудачным деструктором?». Стандартная основа C++ . Проверено 9 марта 2019 г.
  13. ^ Ричард Смит (21 марта 2017 г.). «Рабочий проект стандарта языка программирования C++» (PDF) . Проверено 9 марта 2019 г.
  14. ^ Страуструп, Бьярн ; Саттер, Херб (3 августа 2020 г.). «Основные рекомендации по C++» . Проверено 15 августа 2020 г.
  15. ^ «У меня слишком много блоков попыток; что я могу с этим поделать?». Стандартная основа C++ . Проверено 9 марта 2019 г.
  16. ^ «Указание атрибутов переменных». Использование коллекции компиляторов GNU (GCC) . Проект ГНУ . Проверено 9 марта 2019 г.
  17. ^ Веймер, Уэстли; Некула, Джордж К. (2008). «Исключительные ситуации и надежность программ» (PDF) . Транзакции ACM в языках и системах программирования . Том. 30, нет. 2.
  18. ^ илдьярн (5 апреля 2011 г.). «RAII и размотка стека». Переполнение стека . Проверено 9 марта 2019 г.
  19. ^ Gamelab2018 - Дизайнерские решения Джона Блоу о создании Jai, нового языка для игровых программистов на YouTube.
  20. ^ «Расширение Python с помощью C или C++: счетчик ссылок». Расширение и встраивание интерпретатора Python . Фонд программного обеспечения Python . Проверено 9 марта 2019 г.
  21. ^ Хоббс (08 февраля 2011 г.). «Поддерживает ли PHP шаблон RAII? Как?» . Проверено 9 марта 2019 г.
  22. ^ «gc — Интерфейс сборщика мусора» . Стандартная библиотека Python . Фонд программного обеспечения Python . Проверено 9 марта 2019 г.

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

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