Получение ресурсов — это инициализация
Эта статья нуждается в дополнительных цитатах для проверки . ( декабрь 2012 г. ) |
Получение ресурса — это инициализация ( RAII ) [1] это идиома программирования [2] используется в нескольких объектно-ориентированных статически типизированных языках программирования для описания определенного поведения языка. В RAII хранение ресурса является инвариантом класса и привязано к времени жизни объекта . Выделение (или получение) ресурсов выполняется во время создания объекта (в частности, инициализации) с помощью конструктора , тогда как освобождение (освобождение) ресурсов выполняется во время уничтожения объекта (в частности, завершения) с помощью деструктора . Другими словами, для успешной инициализации получение ресурса должно быть успешным. Таким образом, ресурс гарантированно будет удерживаться между завершением инициализации и началом финализации (хранение ресурсов является инвариантом класса) и будет удерживаться только тогда, когда объект жив. Таким образом, если нет утечек объектов, нет и утечек ресурсов .
RAII наиболее тесно связан с C++ , где он зародился, а также с Ada . [3] Налейте , [4] и Руст . [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 <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++ гарантирует, что все объекты с автоматическим сроком хранения (локальные переменные) уничтожаются в конце охватывающей области видимости в порядке, обратном их созданию. [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
в С++98 и boost::shared_ptr
в библиотеках Boost .
Также сообщения можно отправлять на сетевые ресурсы с помощью RAII. В этом случае объект RAII отправит сообщение в сокет в конце конструктора, когда его инициализация будет завершена. Он также отправит сообщение в начале деструктора, когда объект вот-вот будет уничтожен. Такая конструкция может использоваться в клиентском объекте для установления соединения с сервером, работающим в другом процессе.
Расширения «очистки» компилятора
[ редактировать ]И Clang , и коллекция компиляторов GNU реализуют нестандартное расширение языка C для поддержки RAII: атрибут переменной «cleanup». [16] Следующее аннотирует переменную заданной функцией деструктора, которую она будет вызывать, когда переменная выйдет за пределы области видимости:
void example_usage() {
__attribute__((cleanup(fclosep))) FILE *logfile = fopen("logfile.txt", "w+");
fputs("hello logfile!", logfile);
}
В этом примере компилятор обеспечивает вызов функции 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]
Ссылки
[ редактировать ]- ^ Jump up to: а б Страуструп, Бьярне (30 сентября 2017 г.). «Почему C++ не предоставляет конструкцию «наконец»?» . Проверено 9 марта 2019 г.
- ^ Саттер, Херб ; Александреску, Андрей (2005). Стандарты кодирования C++ . Серия углубленного изучения C++. Аддисон-Уэсли. п. 24 . ISBN 978-0-321-11358-0 .
- ^ «Жемчужина № 70: Идиома блокировки прицела» . АдаКор . Проверено 21 мая 2021 г.
- ^ Проект Валадате. «Разрушение» . Учебное пособие по Vala версии 0.30 . Проверено 21 мая 2021 г.
- ^ «RAII — ржавчина на примере» . doc.rust-lang.org . Проверено 22 ноября 2020 г.
- ^ Страуструп 1994 , 16.5 Управление ресурсами, стр. 388–89.
- ^ Страуструп 1994 , 16.1 Обработка исключений: Введение, стр. 383–84.
- ^ Страуструп 1994 , с. 389. Я назвал эту технику «приобретение ресурсов — это инициализация».
- ^ Артур Чайковский (6 ноября 2012 г.). «Изменить официальный RAII на CADRe» . Стандарт ISO C++ — будущие предложения . Группы Google . Проверено 9 марта 2019 г.
- ^ Чоу, Аллен (01 октября 2014 г.). «Управление ресурсами на основе объема (RAII)» . Проверено 9 марта 2019 г.
- ^ Ричард Смит (21 марта 2017 г.). «Рабочий проект стандарта языка программирования C++» (PDF) . п. 151, раздел §9.6 . Проверено 7 сентября 2023 г.
- ^ «Как я могу справиться с неудачным деструктором?» . Стандартная основа C++ . Проверено 9 марта 2019 г.
- ^ Ричард Смит (21 марта 2017 г.). «Рабочий проект стандарта языка программирования C++» (PDF) . Проверено 9 марта 2019 г.
- ^ Страуструп, Бьярне ; Саттер, Херб (3 августа 2020 г.). «Основные рекомендации по C++» . Проверено 15 августа 2020 г.
- ^ «У меня слишком много блоков попыток. Что я могу с этим поделать?» . Стандартная основа C++ . Проверено 9 марта 2019 г.
- ^ «Указание атрибутов переменных» . Использование коллекции компиляторов GNU (GCC) . Проект ГНУ . Проверено 9 марта 2019 г.
- ^ Веймер, Уэстли; Некула, Джордж К. (2008). «Исключительные ситуации и надежность программ» (PDF) . Транзакции ACM в языках и системах программирования . Том. 30, нет. 2.
- ^ илдьярн (05 апреля 2011 г.). «RAII и размотка стека» . Переполнение стека . Проверено 9 марта 2019 г.
- ^ Gamelab2018 - Дизайнерские решения Джона Блоу по созданию Jai, нового языка для игровых программистов на YouTube
- ^ «Расширение Python с помощью C или C++: количество ссылок» . Расширение и встраивание интерпретатора Python . Фонд программного обеспечения Python . Проверено 9 марта 2019 г.
- ^ Хоббс (08 февраля 2011 г.). «Поддерживает ли PHP шаблон RAII? Как?» . Проверено 9 марта 2019 г.
- ^ «gc — интерфейс сборщика мусора» . Стандартная библиотека Python . Фонд программного обеспечения Python . Проверено 9 марта 2019 г.
Дальнейшее чтение
[ редактировать ]- Страуструп, Бьярн (1994). Проектирование и эволюция C++ . Аддисон-Уэсли. Бибкод : 1994декабрь..книга.....S . ISBN 978-0-201-54330-8 .
Внешние ссылки
[ редактировать ]- Пример главы: « Совет № 67: отказ от использования приобретения ресурсов — это инициализация », Стивен К. Дьюхерст
- Интервью: « Разговор с Бьерном Страуструпом » Билла Веннерса
- Статья: « Закон большой двойки », Бьорн Карлссон и Мэтью Уилсон.
- Статья: « Реализация идиомы «приобретение ресурсов — это инициализация »» Дэнни Калева
- « RAII, динамические объекты и фабрики в C++ ». Статья: Роланд Пибингер
- RAII в Delphi: « Однострочный RAII в Delphi » Барри Келли
- Руководство: RAII на C++ от W3computing