Что означает «Приобретение ресурсов» - «Инициализация» (RAII)? - PullRequest
230 голосов
/ 23 февраля 2010

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

Ответы [ 7 ]

326 голосов
/ 05 августа 2013

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

С RAII один

class ManagedResourceHandle {
public:
   ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
   ~ManagedResourceHandle() {delete rawHandle; }
   ... // omitted operator*, etc
private:
   RawResourceHandle* rawHandle;
};

ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();

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

111 голосов
/ 23 февраля 2010

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

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

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

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

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

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

45 голосов
/ 22 ноября 2010

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

В самом сердце этой идиомы есть инкапсулирующие ресурсы (куски памяти, открытые файлы, разблокированные мьютексы, вы называете это) в локальные, автоматические объекты и имеющие деструктор этого объекта освобождает ресурс, когда объект уничтожается в конце области видимости, к которой он принадлежит:

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

11 голосов
/ 05 августа 2013

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

  1. Получение всех ресурсов
  2. Использование ресурсов
  3. Освобождение ресурсов

Где

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

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

8 голосов
/ 12 ноября 2018

Время жизни объекта определяется его областью действия. Однако иногда нам нужно или полезно создавать объект, который живет независимо от области, в которой он был создан. В C ++ оператор new используется для создания такого объекта. А для уничтожения объекта можно использовать оператор delete. Объекты, созданные оператором new, распределяются динамически, то есть распределяются в динамической памяти (также называемой heap или free store ). Таким образом, объект, созданный 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 , сокращение от Управление ресурсами, связанными с областью действия .

5 голосов
/ 11 июля 2018

Класс 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 голосов
/ 28 января 2016

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

...