Що означає "Придбання ресурсів" - ініціалізація (RAII)?


283

Що означає "Придбання ресурсів" - ініціалізація (RAII)?



13
Це те, що приводить мене додому. stroustrup.com/bs_faq2.html#finally
Hal Canary

2
Посилання Майкрософт з 3 реченнями та 2 прикладами, але дуже зрозуміло! msdn.microsoft.com/en-us/library/hh438480.aspx
Gab 是 好人

Відповіді:


374

Це дійсно жахлива назва неймовірно потужної концепції, і, можливо, одна з речей № 1, якої розробники C ++ пропускають, переходячи на інші мови. Дещо було зроблено рух, щоб спробувати перейменувати цю концепцію як Управління ресурсами , пов’язані з обсягами , хоча, здається, це ще не наздогнало.

Коли ми говоримо "Ресурс", ми не маємо на увазі лише пам'ять - це можуть бути файлові ручки, мережеві сокети, ручки бази даних, об'єкти GDI ... Коротше кажучи, речі, які у нас є обмеженими, і тому нам потрібно вміти контролювати їх використання. Аспект 'пов'язаний зі сферою дії' означає, що термін експлуатації об'єкта пов'язаний з областю змінної, тому коли змінна виходить із сфери дії, деструктор вивільнить ресурс. Дуже корисною властивістю цього є те, що це сприяє підвищенню безпеки винятків. Наприклад, порівняйте це:

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();

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


2
@the_mandrill: Я спробував ideone.com/1Jjzuc цю програму. Але виклику деструктора немає. Tomdalling.com/blog/software-design/… говорить, що C ++ гарантує, що деструктор об'єктів у стеці буде викликаний, навіть якщо буде викинуто виняток. Отже, чому деструктор не виконав тут? Чи просочився мій ресурс чи він ніколи не буде звільнений чи звільнений?
Деструктор

8
Виняток викинуто, але ви його не ловите, тому додаток припиняється. Якщо ви завершите спробу {} catch () {}, це працює як слід: ideone.com/xm2GR9
the_mandrill

2
Не зовсім впевнений, чи Scope-Boundнайкращий вибір імені тут, оскільки специфікатори класу зберігання разом із областю визначають тривалість зберігання об'єкта. Звуження, зроблене для обмеження сфери, може бути корисним спрощенням, однак воно не на 100% точне
SebNag

125

Це ідіома програмування, яка коротко означає, що ви

  • інкапсулювати ресурс у клас (конструктор якого зазвичай - але не обов'язково ** - набуває ресурс, а його деструктор завжди випускає його)
  • використовувати ресурс через локальний екземпляр класу *
  • ресурс автоматично звільняється, коли об’єкт виходить із сфери застосування

Це гарантує, що все, що трапиться під час використання ресурсу, з часом вивільняється (через нормальне повернення, знищення об'єкта, що міститься, або викид, який викидається).

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

* Оновлення: "локальний" може означати локальну змінну або нестатичну змінну члена класу. В останньому випадку змінна-член ініціалізується та знищується об'єктом власника.

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


1
AFAIK, абревіатура не означає, що об'єкт повинен бути в локальній змінній (стек). Це може бути змінною членом іншого об'єкта, тому при знищенні об'єкта 'holding' об'єкт-член також знищується і ресурс звільняється. Насправді, я думаю, що абревіатура конкретно означає лише те, що немає open()/ close()методів для ініціалізації та випуску ресурсу, лише конструктор та деструктор, тому "утримання" ресурсу - це лише час експлуатації об'єкта, незалежно від того, чи буде це життя обробляється контекстом (стек) або явно (динамічний аллок)
Хав'єр

1
Насправді ніщо не говорить про те, що ресурс повинен бути придбаний у конструкторі. Файлові потоки, рядки інших контейнерів роблять це, але ресурс може так само передаватися конструктору, як це зазвичай буває з розумними покажчиками. Оскільки ваша відповідь є найбільш актуальною, ви, можливо, захочете це виправити.
sbi

Це не абревіатура, це абревіатура. IIRC більшість людей вимовляє це "ар-е-а-а-а", тому він насправді не може відповідати абревіатурі, як, наприклад, DARPA, яка вимовляється DARPA замість написаної. Крім того, я б сказав, що RAII - це парадигма, а не просто ідіома.
dtech

@ Peter Torok: Я спробував ideone.com/1Jjzuc цю програму. Але виклику деструктора немає. Tomdalling.com/blog/software-design / ... каже , що гарантує , що C ++ деструктор об'єктів в стеку буде викликатися, навіть якщо виняток. Отже, чому деструктор не виконав тут? Чи просочився мій ресурс чи він ніколи не буде звільнений чи звільнений?
Деструктор

50

"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 який реалізує своєрідну семантику переміщення: Об'єкт належить лише одному вказівнику, а спроба скопіювати об’єкт призведе до (через синтаксичний хакер) передачі права власності на об'єкт цілі операції копіювання.


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), так сказав сам Stroustrup.
sbi

21

Термін експлуатації об'єкта визначається його обсягом. Однак іноді нам потрібно або корисно створити об’єкт, який живе незалежно від сфери, де він був створений. У C ++ оператор newвикористовується для створення такого об’єкта. А для знищення об’єкта deleteможе бути використаний оператор . Об'єкти, створені оператором new, динамічно розподіляються, тобто розподіляються в динамічній пам'яті (також називається купою або вільним сховищем ). Отже, об’єкт, який було створено компанією, newбуде існувати, поки явно не буде знищено за допомогою delete.

Деякі помилки, які можуть виникнути при використанні, newі deleteце:

  • Витіклий об'єкт (або пам'ять): використовуючи newдля виділення об'єкта та забуття на deleteоб'єкт.
  • Передчасне видалення (або звисання посилання ): тримайте інший вказівник на об'єкт, deleteоб'єкт, а потім використовуйте інший покажчик.
  • Подвійне видалення : спроба deleteоб'єкта двічі.

Як правило, переважні масштабні змінні. Однак RAII можна використовувати як альтернативу newта deleteзробити об’єкт живим незалежно від його сфери. Така методика полягає у підведенні вказівника на об’єкт, який був виділений на купі, та розміщення його в об'єкті ручки / менеджера . Останній має деструктор, який подбає про знищення об’єкта. Це гарантуватиме, що об'єкт буде доступний будь-якій функції, яка хоче отримати доступ до нього, і що об'єкт буде знищений, коли закінчується термін експлуатації об'єкта обробки , без необхідності явного очищення.

Прикладами зі стандартної бібліотеки 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 , скорочення для управління ресурсами .


1
"SBRM" має для мене набагато більше сенсу. Я прийшов до цього питання, тому що думав, що розумію RAII, але назва мене відкидає, почувши це, описане натомість як "Управління ресурсами, пов'язаними з обсягами", дало мені можливість зрозуміти, що я дійсно розумію концепцію.
JShorthouse

Я не впевнений, чому це не було позначено як відповідь на запитання. Це дуже ретельна та добре написана відповідь, дякую @elmiomar
Абдельрахман Шоман

13

Книга Програмування на C ++ з розкритими шаблонами дизайну описує RAII як:

  1. Придбання всіх ресурсів
  2. Використання ресурсів
  3. Вивільнення ресурсів

Де

  • Ресурси реалізуються у вигляді класів, а всі вказівники мають класові обгортки навколо них (що робить їх розумними покажчиками).

  • Ресурси набуваються шляхом виклику їх конструкторів, а вивільнення неявно (у зворотному порядку придбання) шляхом виклику їхніх деструкторів.


1
@Brandin Я відредагував свою публікацію, щоб читачі зосереджувались на важливому вмісті, а не обговорювали сіру область закону про авторські права того, що є справедливим.
Денніс

7

До класу RAII є три частини:

  1. Ресурс відмовляється від деструктора
  2. Екземпляри класу виділяються стеком
  3. Ресурс набувається в конструкторі. Ця частина необов’язкова, але поширена.

RAII означає "Придбання ресурсів - ініціалізація." Частина RAII "придбання ресурсів" - це те, коли ви починаєте щось, що має бути закінчено пізніше, наприклад:

  1. Відкриття файлу
  2. Виділення деякої пам’яті
  3. Придбання замка

Частина "є ініціалізацією" означає, що придбання відбувається всередині конструктора класу.

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.