RAII
자원 획득이 초기화다 또는 RAII는 C++ 프로그래밍 기법 [1] [2] 으로, 사용 전에 반드시 획득해야 하는 자원(할당된 힙 메모리, 실행 스레드, 열린 소켓, 열린 파일, 잠긴 뮤텍스, 디스크 공간, 데이터베이스 연결—제한된 공급량이 존재하는 모든 것)의 수명 주기를 객체의 수명 에 바인딩합니다.
RAII는 리소스가 객체에 접근할 수 있는 모든 함수에서 사용 가능하도록 보장합니다(리소스 가용성은 클래스 불변식 으로, 런타임 중복 검사를 제거합니다). 또한 모든 리소스가 해당 제어 객체의 수명이 종료될 때 획득 순서의 역순으로 해제됨을 보장합니다. 마찬가지로, 리소스 획득이 실패할 경우(생성자가 예외와 함께 종료될 때), 완전히 생성된 모든 멤버 및 기반 하위 객체가 획득한 모든 리소스는 초기화 순서의 역순으로 해제됩니다. 이는 핵심 언어 기능( 객체 수명 , 범위 종료 , 초기화 순서 및 스택 풀기 )을 활용하여 리소스 누수를 제거하고 예외 안전성을 보장합니다. 이 기법의 다른 이름은 범위-바인딩 리소스 관리 (SBRM)으로, RAII 객체의 수명이 범위 종료로 인해 끝나는 기본 사용 사례에서 비롯되었습니다.
RAII는 다음과 같이 요약할 수 있습니다:
- 각 리소스를 클래스로 캡슐화합니다, 여기서
-
- 생성자는 리소스를 획득하고 모든 클래스 불변 조건을 설정하거나, 이를 수행할 수 없는 경우 예외를 발생시킵니다,
- 소멸자는 리소스를 해제하고 절대 예외를 발생시키지 않습니다;
- 항상 RAII-클래스의 인스턴스를 통해 리소스를 사용하십시오. 해당 클래스는 다음 중 하나를 수행합니다
-
- 자동 저장 기간 또는 임시 수명 자체를 가지거나,
- 자동 또는 임시 객체의 수명에 의해 제한되는 수명을 가집니다.
|
이동 의미론은 컨테이너 내부 및 외부 객체 간, 그리고 스레드 간에 자원과 소유권의 이전을 가능하게 하면서도 자원 안전성을 보장합니다. |
(since C++11) |
open()
/
close()
,
lock()
/
unlock()
, 또는
init()
/
copyFrom()
/
destroy()
멤버 함수를 가진 클래스들은 RAII가 아닌 클래스의 전형적인 예시입니다:
std::mutex m; void bad() { m.lock(); // 뮤텍스 획득 f(); // f()가 예외를 발생시키면 뮤텍스가 절대 해제되지 않음 if (!everything_ok()) return; // 조기 반환, 뮤텍스가 절대 해제되지 않음 m.unlock(); // bad()가 이 문장에 도달하면 뮤텍스가 해제됨 } void good() { std::lock_guard<std::mutex> lk(m); // RAII 클래스: 뮤텍스 획득이 초기화임 f(); // f()가 예외를 발생시키면 뮤텍스가 해제됨 if (!everything_ok()) return; // 조기 반환, 뮤텍스가 해제됨 } // good()이 정상적으로 반환되면 뮤텍스가 해제됨
표준 라이브러리
C++ 라이브러리 클래스 중 자체 리소스를 관리하는 클래스들은 RAII를 따릅니다: std::string , std::vector , std::jthread (C++20부터) 그리고 많은 다른 클래스들은 생성자에서 리소스를 획득하고(오류 발생 시 예외를 던짐), 소멸자에서 이를 해제하며(절대 예외를 던지지 않음), 명시적인 정리가 필요하지 않습니다.
|
또한 표준 라이브러리는 사용자 제공 리소스를 관리하기 위한 여러 RAII 래퍼를 제공합니다:
|
(C++11부터) |
참고 사항
RAII는 사용 전에 획득되지 않는 자원의 관리에는 적용되지 않습니다: CPU 시간, 코어 가용성, 캐시 용량, 엔트로피 풀 용량, 네트워크 대역폭, 전력 소비, 스택 메모리. 이러한 자원들에 대해서는 C++ 클래스 생성자가 객체 수명 동안 자원 가용성을 보장할 수 없으며, 다른 자원 관리 수단을 사용해야 합니다.