Что означает «Приобретение ресурсов» - «Инициализация» (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.