Что означает «Приобретение ресурсов» - «Инициализация» (RAII)?


283

Что означает «Приобретение ресурсов» - «Инициализация» (RAII)?



13
Это то, что ведет меня домой. stroustrup.com/bs_faq2.html#finally
— Hal Canary

2
Справочник Microsoft с 3 предложениями и 2 примерами, но очень ясный msdn.microsoft.com/en-us/library/hh438480.aspx
— Gab 是 好人

Ответы:


374

Это действительно ужасное название для невероятно мощной концепции, и, возможно, одна из вещей номер один, которую пропускают разработчики 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();

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


2
@the_mandrill: я пробовал ideone.com/1Jjzuc эту программу. Но звонка деструктора нет. В tomdalling.com/blog/software-design/… говорится, что C ++ гарантирует, что деструктор объектов в стеке будет вызван, даже если выдается исключение. Итак, почему деструктор не был выполнен здесь? Утечен ли мой ресурс или он никогда не будет освобожден или освобожден?
— Разрушитель

8
Выдается исключение, но вы его не перехватываете, поэтому приложение завершается. Если вы добавляете try {} catch () {}, то все работает как положено: ideone.com/xm2GR9
— the_mandrill

2
Не совсем уверен, что Scope-Boundэто лучший выбор имени, поскольку спецификаторы класса хранения вместе с областью действия определяют продолжительность хранения сущности. Сужение этого до границ области видимости может быть полезным упрощением, однако оно не является точным на 100%
— SebNag

125

Это идиома программирования, которая вкратце означает, что вы

  • инкапсулировать ресурс в класс (чей конструктор обычно - но не обязательно ** - получает ресурс, а его деструктор всегда освобождает его)
  • использовать ресурс через локальный экземпляр класса *
  • ресурс автоматически освобождается, когда объект выходит из области видимости

Это гарантирует, что что бы ни происходило во время использования ресурса, оно в конечном итоге будет освобождено (будь то из-за нормального возврата, уничтожения содержащего объекта или создания исключения).

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

* Обновление: «локальный» может означать локальную переменную или нестатическую переменную-член класса. В последнем случае переменная-член инициализируется и уничтожается вместе с объектом-владельцем.

** Update2: как указывал @sbi, ресурс - хотя он часто выделяется внутри конструктора - также может быть выделен снаружи и передан как параметр.


1
AFAIK, аббревиатура не означает, что объект должен находиться в локальной переменной (стеке). Это может быть переменная-член другого объекта, поэтому, когда «удерживающий» объект уничтожается, объект-член также уничтожается, и ресурс освобождается. На самом деле, я думаю, что аббревиатура, в частности, означает только то, что нет никаких open()/ close()методов для инициализации и освобождения ресурса, только конструктор и деструктор, поэтому «удержание» ресурса - это просто время жизни объекта, независимо от того, является ли это время жизни обрабатывается контекстом (стек) или явно (динамическое выделение)
— Хавьер

1
На самом деле ничего не говорит, что ресурс должен быть получен в конструкторе. Файловые потоки, строки других контейнеров делают это, но ресурс может также быть передан в конструктор, как это обычно бывает с умными указателями. Поскольку ваш ответ является самым популярным, вы можете исправить это.
— 2012 г.,

Это не аббревиатура, это аббревиатура. IIRC большинство людей называют это «ar ayyy», так что на самом деле это не соответствует аббревиатуре, например, DARPA, которая произносится как DARPA, а не пишется по буквам. Кроме того, я бы сказал, что RAII - это парадигма, а не просто идиома.
— 2011 года

@Peter Torok: я пробовал ideone.com/1Jjzuc эту программу. Но звонка деструктора нет. В tomdalling.com/blog/software-design/… говорится, что C ++ гарантирует, что деструктор объектов в стеке будет вызван, даже если выдается исключение. Итак, почему деструктор не был выполнен здесь? Утечен ли мой ресурс или он никогда не будет освобожден или освобожден?
— Разрушитель

50

«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 который реализует своего рода семантику перемещения: объект принадлежит только одному указателю, и попытка скопировать объект приведет (посредством синтаксического хакерства) к передаче права собственности на объект цели операции копирования.


4
std::auto_ptrУстаревшая версия std::unique_ptr. std::auto_ptrСемантика симулированного перемещения, насколько это было возможно в C ++ 98, std::unique_ptrиспользует новую семантику перемещения C ++ 11. Новый класс был создан, потому что семантика перемещения в C ++ 11 является более явной (требуется, std::moveкроме как из временной), в то время как он был установлен по умолчанию для любой копии из неконстантной в std::auto_ptr.
— Ян Худек

@JiahaoCai: Однажды, много лет назад (в Usenet), сам Страуструп сказал об этом.
— ВОО

21

Время жизни объекта определяется его областью действия. Однако иногда нам нужно или полезно создавать объект, который живет независимо от области, в которой он был создан. В 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 .


1
«SBRM» имеет для меня гораздо больше смысла. Я пришел к этому вопросу, потому что мне показалось, что я понимаю RAII, но это имя меня оттолкнуло, потому что вместо того, чтобы описать его как «Управление ресурсами, связанными с областью действия», я сразу понял, что действительно понимаю эту концепцию.
— JShorthouse

Я не уверен, почему это не было отмечено как ответ на вопрос. Это очень подробный и хорошо написанный ответ, спасибо @elmiomar
— Абдельрахман Шоман

13

Книга C ++ Программирование с раскрытыми шаблонами проектирования описывает RAII как:

  1. Приобретение всех ресурсов
  2. Использование ресурсов
  3. Выпуск ресурсов

куда

  • Ресурсы реализованы в виде классов, и все указатели имеют обертки классов вокруг них (что делает их умными указателями).

  • Ресурсы приобретаются путем вызова их конструкторов и освобождаются неявно (в обратном порядке получения) путем вызова их деструкторов.


1
@ Брандин Я отредактировал свой пост, чтобы читатели сосредоточились на содержании, которое имеет значение, а не обсуждали серую область закона об авторском праве того, что представляет собой добросовестное использование.
— Деннис

7

Класс RAII состоит из трех частей:

  1. Ресурс передан в деструктор
  2. Экземпляры класса размещаются в стеке
  3. Ресурс приобретается в конструкторе. Эта часть необязательна, но распространена.

RAII расшифровывается как «Приобретение ресурсов - это инициализация». Часть RAII «получение ресурсов» - это то, где вы начинаете что-то, что должно быть закончено позже, например:

  1. Открытие файла
  2. Выделение памяти
  3. Получение блокировки

Часть «is initialization» означает, что получение происходит внутри конструктора класса.

https://www.tomdalling.com/blog/software-design/resource-acquisition-is-initialisation-raii-explained/


5

Ручное управление памятью - это кошмар, который программисты придумали, чтобы избежать способов с момента изобретения компилятора. Языки программирования с сборщиками мусора облегчают жизнь, но за счет производительности. В этой статье - « Устранение сборщика мусора: путь RAII» , инженер Toptal Питер Гудспид-Никлаус рассказывает нам историю сборщиков мусора и объясняет, как понятия собственности и заимствования могут помочь устранить сборщики мусора, не ставя под угрозу их гарантии безопасности.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.