Перейти до вмісту

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. Архівована копія. Архів оригіналу за 4 липня 2014. Процитовано 11 липня 2014.{{cite web}}: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання)
  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. Архів оригіналу за 17 лютого 2013. Процитовано 12 лютого 2013.
  4. What's the order that local objects are destructed?. Архів оригіналу за 9 жовтня 2014. Процитовано 12 лютого 2013.
  5. too-many-trycatch-blocks. Архів оригіналу за 11 травня 2014. Процитовано 12 лютого 2013.