Что означает «Приобретение ресурсов» - «Инициализация» (RAII)?
Что означает «Приобретение ресурсов» - «Инициализация» (RAII)?
Ответы:
Это действительно ужасное название для невероятно мощной концепции, и, возможно, одна из вещей номер один, которую пропускают разработчики C ++ при переходе на другие языки. Было небольшое движение, чтобы попытаться переименовать эту концепцию в Scope-Bound Resource Management , хотя, похоже, она еще не завоевала популярность.
Когда мы говорим «Ресурс», мы имеем в виду не только память - это могут быть файловые дескрипторы, сетевые сокеты, дескрипторы базы данных, объекты GDI ... Короче говоря, вещи, которые у нас ограничены, и поэтому мы должны иметь возможность контролировать их использование. Аспект «Scope-bound» означает, что время жизни объекта привязано к области видимости переменной, поэтому, когда переменная выходит из области видимости, деструктор освобождает ресурс. Очень полезным свойством этого является то, что оно обеспечивает большую исключительную безопасность. Например, сравните это:
RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation(); // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks
С РАИИ
class ManagedResourceHandle {
public:
ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
~ManagedResourceHandle() {delete rawHandle; }
... // omitted operator*, etc
private:
RawResourceHandle* rawHandle;
};
ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
В этом последнем случае, когда выбрасывается исключение и стек разматывается, локальные переменные уничтожаются, что обеспечивает очистку нашего ресурса и отсутствие утечки.
Scope-Boundэто лучший выбор имени, поскольку спецификаторы класса хранения вместе с областью действия определяют продолжительность хранения сущности. Сужение этого до границ области видимости может быть полезным упрощением, однако оно не является точным на 100%
Это идиома программирования, которая вкратце означает, что вы
Это гарантирует, что что бы ни происходило во время использования ресурса, оно в конечном итоге будет освобождено (будь то из-за нормального возврата, уничтожения содержащего объекта или создания исключения).
Это широко используемая хорошая практика в C ++, потому что, кроме того, что это безопасный способ работы с ресурсами, он также делает ваш код намного чище, так как вам не нужно смешивать код обработки ошибок с основной функциональностью.
* Обновление: «локальный» может означать локальную переменную или нестатическую переменную-член класса. В последнем случае переменная-член инициализируется и уничтожается вместе с объектом-владельцем.
** Update2: как указывал @sbi, ресурс - хотя он часто выделяется внутри конструктора - также может быть выделен снаружи и передан как параметр.
open()/ close()методов для инициализации и освобождения ресурса, только конструктор и деструктор, поэтому «удержание» ресурса - это просто время жизни объекта, независимо от того, является ли это время жизни обрабатывается контекстом (стек) или явно (динамическое выделение)
«RAII» расшифровывается как «Resource Acquisition is Initialization» и на самом деле является неправильным обозначением, поскольку речь идет не о получении ресурсов (и инициализации объекта), к которому оно относится, а об освобождении ресурса (посредством уничтожения). объекта). ).
Но RAII - это имя, которое мы получили, и оно прилипает.
По своей сути, эта идиома содержит инкапсулирующие ресурсы (куски памяти, открытые файлы, незаблокированные мьютексы, you-name-it) в локальных автоматических объектах , и деструктор этого объекта освобождает ресурс, когда объект уничтожается на конец области, к которой он относится:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Конечно, объекты не всегда локальные, автоматические объекты. Они также могут быть членами класса:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Если такие объекты управляют памятью, их часто называют «умными указателями».
Есть много вариантов этого. Например, в первом фрагменте кода возникает вопрос, что произойдет, если кто-то захочет скопировать obj. Самый простой выход - просто запретить копирование. std::unique_ptr<>это делает умный указатель, являющийся частью стандартной библиотеки, как это предусмотрено в следующем стандарте C ++.
Еще один такой умный указатель, std::shared_ptrимеет «общее владение» ресурсом (динамически размещаемым объектом), который он содержит. То есть его можно свободно копировать, и все копии ссылаются на один и тот же объект. Интеллектуальный указатель отслеживает, сколько копий ссылается на один и тот же объект, и удаляет его при уничтожении последнего.
Третий вариант представленstd::auto_ptr который реализует своего рода семантику перемещения: объект принадлежит только одному указателю, и попытка скопировать объект приведет (посредством синтаксического хакерства) к передаче права собственности на объект цели операции копирования.
std::auto_ptrУстаревшая версия std::unique_ptr. std::auto_ptrСемантика симулированного перемещения, насколько это было возможно в C ++ 98, std::unique_ptrиспользует новую семантику перемещения C ++ 11. Новый класс был создан, потому что семантика перемещения в C ++ 11 является более явной (требуется, std::moveкроме как из временной), в то время как он был установлен по умолчанию для любой копии из неконстантной в std::auto_ptr.
Время жизни объекта определяется его областью действия. Однако иногда нам нужно или полезно создавать объект, который живет независимо от области, в которой он был создан. В C ++ оператор newиспользуется для создания такого объекта. А чтобы уничтожить объект, deleteможно использовать оператор . Объекты, созданные оператором new, распределяются динамически, то есть распределяются в динамической памяти (также называемой кучей или свободным хранилищем ). Таким образом, объект, который был создан, newбудет продолжать существовать до тех пор, пока не будет явно уничтожен с помощьюdelete .
Некоторые ошибки, которые могут возникнуть при использовании newи deleteявляются:
newчтобы выделить объект и забытьdelete объект.deleteобъект, а затем используйте другой указатель.deleteобъекта дважды.Как правило, переменные области видимости являются предпочтительными. Тем не менее, RAII может использоваться как альтернатива newи deleteзаставить объект жить независимо от его объема. Такая техника состоит в том, чтобы взять указатель на объект, который был размещен в куче, и поместить его в объект handle / manager . У последнего есть деструктор, который позаботится об уничтожении объекта. Это будет гарантировать, что объект доступен для любой функции, которая хочет получить к нему доступ, и что объект уничтожается при жизни объекта дескриптора. закончится , без необходимости явной очистки.
Примеры из стандартной библиотеки C ++, которые используют RAII, std::stringи std::vector.
Рассмотрим этот кусок кода:
void fn(const std::string& str)
{
std::vector<char> vec;
for (auto c : str)
vec.push_back(c);
// do something
}
когда вы создаете вектор и помещаете в него элементы, вам не нужно выделять и освобождать такие элементы. Вектор используется newдля выделения пространства для его элементов в куче и deleteдля освобождения этого пространства. Вы, как пользователь вектора, не заботитесь о деталях реализации и будете доверять вектору, чтобы он не просочился. В этом случае вектор является объектом дескриптора своих элементов.
Другие примеры из стандартной библиотеки , которые используют RAII являются std::shared_ptr, std::unique_ptrи std::lock_guard.
Другое название этого метода - SBRM , сокращение от Scope-Bound Resource Management .
Книга C ++ Программирование с раскрытыми шаблонами проектирования описывает RAII как:
куда
Ресурсы реализованы в виде классов, и все указатели имеют обертки классов вокруг них (что делает их умными указателями).
Ресурсы приобретаются путем вызова их конструкторов и освобождаются неявно (в обратном порядке получения) путем вызова их деструкторов.
Класс RAII состоит из трех частей:
RAII расшифровывается как «Приобретение ресурсов - это инициализация». Часть RAII «получение ресурсов» - это то, где вы начинаете что-то, что должно быть закончено позже, например:
Часть «is initialization» означает, что получение происходит внутри конструктора класса.
Ручное управление памятью - это кошмар, который программисты придумали, чтобы избежать способов с момента изобретения компилятора. Языки программирования с сборщиками мусора облегчают жизнь, но за счет производительности. В этой статье - « Устранение сборщика мусора: путь RAII» , инженер Toptal Питер Гудспид-Никлаус рассказывает нам историю сборщиков мусора и объясняет, как понятия собственности и заимствования могут помочь устранить сборщики мусора, не ставя под угрозу их гарантии безопасности.