Resource Acquisition Is Initialization

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку

Resource Acquisition Is Initialization (RAII), перекладається як «Отримання ресурсу є ініціалізація» — програмна ідіома, яка використовується в деяких обектно-орієнтовних мовах програмування, більшою мірою в C++, звідки вона взяла початок, але також застосовується в D, Ada, і Vala.

Відповідно RAII, утримання ресурсу тісно прив'язане до життєвого циклу об'єкта: алокація ресурсу (отримання) відбувається в момент створення об'єкта за допомогою конструктора, а деаллокація (звільнення) відбувається в момент знищення об'єкта за допомогою деструктора. Якщо об'єкти видаляються правильно, витоків ресурсів не відбувається.

Інші назви цієї ідіоми: Constructor Acquires, Destructor Releases (CADRe)[1] і Scope-based Resource Management (SBRM);[2]

Переваги[ред. | ред. код]

Перевагами RAII, як техніки управління ресурсами є те, що вона забезпечує інкапсуляцію, безпечність при виняткових ситуаціях (для відслідковування ресурсів), і локальність (дозволяє написати логіку отримання ресурсу і вивільнення поруч в одному місці). Інкапсуляція забезпечується тим, що логіка управління ресурсами описана один раз в класі, а не в кожному місці де вона використовується. Безпека при виняткових ситуаціях забезпечується для стекових ресурсів (ресурси звільняються в тій самій області видимості в якій вони були створені) завдяки прив'язці до життєвого циклу стекової змінної (локальної змінної об'явленою у даному блоці): якщо виникає виняткова ситуація (англ. exception), і існує відповідних блок відловлювання помилок, єдиний код, який буде завжди виконаний після виходу із даної області видимості, це деструктори об'єктів об'явлених в цій області видимості. Локальність, забезпечується завдяки написанню конструктора і деструктора у визначенні класу поруч один із одним.

Управління ресурсами, таким чином, повинно бути тісно прив'язаним до тривалості життя відповідних об'єктів, щоб отримати автоматичну аллокацію і очищення. Ресурси створюються в процесі ініціалізації, коли немає жодної можливості використати їх раніше, перш ніж вони стануть доступними і звільнити їх гарантовано, навіть у разі виникнення помилок.

Типове застосування[ред. | ред. код]

RAII часто використовується для контролювання замикання м'ютексу (mutex) у багатопоточних застосуваннях. В такому випадку, при знищенні об'єкт гарантовано відпускає м'ютекс. Без застосування RAII існує потенційна загроза взаємного блокування (deadlock) м'ютексів. При використанні RAII, в код, який бере м'ютекс включає в собі логіку в якій м'ютекс буде звільнено коли виконання програми вийде за область видимості об'єкта.

Інше типове застосування, це робота з файлами: ми можемо мати об'єкт, який зберігає посилання на файл, який буде відкритий для запису в конструкторі, і закритий в деструкторі. В обох випадках, RAII гарантує лише те, що ресурс буде звільнено коректно, але програмісту слід конролювати безпеку при виникненні виняткових ситуацій власноруч. Якщо код, який проводить модифікацію даних, не є безпечним, м'ютекс може бути не взятий, або файл закритий так, що структура даних файлу буде пошкоджена.

Управління динамічно виділеними об'єктами також можна здійснювати за допомогою RAII. Для цього стандартна бібліотека C++ 11 має реалізацію так званого розумного вказівника std::unique_ptr для об'єкта з одним власником, і std::shared_ptr для об'єктів, на які існує декілька посилань. Схожі реалізації також існують в C++ 98 — std::auto_ptr і в бібліотеці Boost — boost::shared_ptr.

Приклад на C++11[ред. | ред. код]

Наступний приклад на C++11 демонструє використання RAII для доступу до файлів і синхронізації м'ютексів:

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>

void write_to_file (const std::string & message) {
    // м’ютекс для захисту доступу для файлу
    static std::mutex mutex;

    // взяття м’ютексу перед операціями з файлом
    std::lock_guard<std::mutex> lock(mutex);

    // перевірка відкриття файлу
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("Не можливо відкрити файл");
    
    // запис повідомлення до файлу
    file << message << std::endl;
    
    // файл буде закритий першим при покиданні області видимості (незалежно від виняткових ситуацій)
    // м’ютекс буде звільнений другим (в деструкторі lock)
}

Цей код є безпечним для виняткових ситуацій, оскільки C++ гарантує, що всі стекові об'єкти будуть знищенні після покидання області видимості. Деструктор для обох об'єктів: lock і file гарантовано буде викликаний при поверненні управління із функції, не залежно від того сталася виняткова ситуація (exception) чи ні.[3]

Локальні змінні дозволяють з легкістю керувати кількома ресурсами в одній функції: вони знищуються в зворотньому порядку відповідно до того, як створювалися, а об'єкти знищуються лише тоді, коли вони були вдало створені, якщо не відбулося виняткових ситуацій в момент створення об'єкта.[4]

Використання RAII значно спрощує процедуру управління ресурсами, зменшує об'єми коду і дозволяє пересвідчитися в коректності програми, тому більша частина стандартної бібліотеки C++ дотримується цієї ідіоми.[5]

Примітки[ред. | ред. код]

  1. https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/UnarLCzNPcI
  2. Masterminds of Programming: Conversations with the Creators of Major Programming Languages, с. 4, на «Google Books», O'Reilly Media, Inc., 21 бер. 2009–496 стор.
  3. dtors-shouldnt-throw. Процитовано 12 February 2013. 
  4. What's the order that local objects are destructed?. Процитовано 12 February 2013. 
  5. too-many-trycatch-blocks. Процитовано 12 February 2013.