Что означает «Приобретение ресурсов» - «Инициализация» (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 Питер Гудспид-Никлаус рассказывает нам историю сборщиков мусора и объясняет, как понятия собственности и заимствования могут помочь устранить сборщики мусора, не ставя под угрозу их гарантии безопасности.