Resource Acquisition is Initialization (RAII)이란 무엇입니까?


답변:


374

엄청나게 강력한 개념의 끔찍한 이름이며 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();

후자의 경우 예외가 발생하고 스택이 풀리면 로컬 변수가 파괴되어 리소스가 정리되고 누출되지 않습니다.


2
@ the_mandrill : 나는이 프로그램을 ideone.com/1Jjzuc 시도했다. 그러나 소멸자 호출은 없습니다. tomdalling.com/blog/software-design/…에 따르면 C ++은 예외가 발생하더라도 스택에있는 객체의 소멸자가 호출되도록합니다. 그렇다면 왜 소멸자가 여기에서 실행되지 않았습니까? 자원이 유출되거나 해제되거나 해제되지 않습니까?
소멸자

8
예외가 발생하지만 잡을 수 없으므로 응용 프로그램이 종료됩니다. try {} catch () {}로 감싸면
the_mandrill

2
Scope-Bound스토리지 클래스 지정자가 범위 와 함께 엔티티의 스토리지 기간을 결정 하므로 여기에서 최상의 이름을 선택 하는지 확실하지 않습니다 . 범위를
좁히는

125

이것은 프로그래밍 관용구입니다.

  • 자원을 클래스로 캡슐화합니다 (일반적으로 생성자는 아니지만 반드시 **는 아니지만)-자원을 획득하고 소멸자는 항상 자원을 해제합니다)
  • 클래스의 로컬 인스턴스를 통해 리소스를 사용합니다 *
  • 개체가 범위를 벗어나면 리소스가 자동으로 해제됩니다.

이렇게하면 리소스를 사용하는 동안 발생하는 모든 일이 결국 반환됩니다 (정상적인 반환, 포함 된 객체의 파괴 또는 예외 발생으로 인해).

리소스를 처리하는 안전한 방법이 아니라 오류 처리 코드와 주요 기능을 혼합 할 필요가 없으므로 코드를 훨씬 깨끗하게 만들 수 있기 때문에 C ++에서 널리 사용되는 모범 사례입니다.

* 업데이트 : "local"은 로컬 변수 또는 클래스의 비 정적 멤버 변수를 의미 할 수 있습니다. 후자의 경우 멤버 변수는 소유자 객체로 초기화되고 소멸됩니다.

** Update2 : @sbi가 지적했듯이 리소스는 종종 생성자 내부에 할당되지만 외부에 할당되어 매개 변수로 전달 될 수도 있습니다.


1
AFAIK, 약어는 객체가 로컬 (스택) 변수에 있어야 함을 의미하지 않습니다. 다른 오브젝트의 멤버 변수 일 수 있으므로 'holding'오브젝트가 소멸되면 멤버 오브젝트도 소멸되고 자원이 해제됩니다. 사실, 약어는 구체적으로 리소스를 초기화하고 해제 할 수있는 open()/ close()메소드가 없으며 생성자와 소멸자 만 있음을 의미 하므로 리소스의 '보유'는 객체의 수명입니다. 문맥 (스택) 또는 명시 적 (동적 할당)에 의해 처리됨
Javier

1
실제로 생성자에서 리소스를 가져와야한다는 것은 없습니다. 파일 스트림, 다른 컨테이너가 수행하는 문자열이지만 일반적으로 스마트 포인터의 경우와 마찬가지로 리소스가 생성자 에게 전달 될 수도 있습니다 . 귀하의 의견이 가장 많이 답변 된 것이므로이 문제를 해결하고자 할 수 있습니다.
sbi

약어가 아니며 약어입니다. IIRC 대부분의 사람들은 그것을 "ar ey ay ay"라고 발음하므로 DARPA와 같은 약어가 실제로 유효하지 않습니다. 또한 RAII는 단순한 관용구가 아니라 패러다임이라고합니다.
dtech

@ Peter Torok : 나는 이 프로그램을 ideone.com/1Jjzuc을 시도했다 . 그러나 소멸자 호출은 없습니다. tomdalling.com/blog/software-design/...는 예외가 발생하더라도, C ++ 보장이 스택에있는 객체의 소멸자가 호출 될 것이라고 말한다. 그렇다면 왜 소멸자가 여기에서 실행되지 않았습니까? 자원이 유출되거나 해제되거나 해제되지 않습니까?
소멸자

50

"RAII"는 "Resource Acquisition is Initialization (자원 획득은 초기화)"의 약자이며, 관련된 자원 획득 (및 객체의 초기화) 이 아니기 때문에 (오브젝트 의 파괴 를 통해) 자원을 방출 하기 때문에 실제로 매우 잘못된 이름 입니다. ). 그러나 RAII는 우리가 얻은 이름이며 그대로 유지됩니다.

바로이 관용구는 로컬, 자동 객체에 리소스 (메모리 청크, 열린 파일, 잠금 해제 된 뮤텍스, you-name-it)를 캡슐화 하고 해당 객체가 소멸 될 때 해당 객체의 소멸자가 리소스를 해제하도록하는 기능이 있습니다. 범위의 끝 :

{
  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_ptrC ++ 98에서 가능한 많은 종류의 시뮬레이션 된 이동 의미 std::unique_ptr체계는 C ++ 11의 새로운 이동 의미 체계를 사용합니다. C ++ 11의 이동 의미가보다 명시 적이므로 ( std::move임시 제외)에 in-const가 아닌 모든 사본에 대해 기본값이 설정되었으므로 새 클래스가 작성 되었습니다 std::auto_ptr.
Jan Hudec

@JiahaoCai : 몇 년 전 (유즈넷에서) Stroustrup 자신도 그렇게 말했습니다.
sbi

21

개체의 수명은 범위에 따라 결정됩니다. 그러나 때로는 생성 된 범위와 독립적으로 존재하는 객체를 생성해야하거나 유용합니다. C ++에서 연산자 new는 이러한 객체를 만드는 데 사용됩니다. 그리고 물체를 파괴하기 위해 연산자를 delete사용할 수 있습니다. 운영자 new가 생성 한 객체 는 동적으로 할당됩니다. 즉 동적 메모리 ( heap 또는 free store 라고도 함)에 할당됩니다 . 따라서에 의해 생성 된 객체 new는를 사용하여 명시 적으로 파괴 될 때까지 계속 존재합니다 delete.

사용할 때 발생할 수있는 몇 가지 실수 는 다음 newdelete같습니다.

  • 누출 된 객체 (또는 메모리) : new객체를 할당하고 잊어 버리는 데 사용 delete합니다.
  • 조기 삭제 (또는 매달린 참조 ) : 객체에 delete대한 다른 포인터를 잡고 객체를 가리킨 다음 다른 포인터를 사용하십시오.
  • 이중 삭제 : delete객체를 두 번 시도합니다 .

일반적으로 범위가 지정된 변수가 선호됩니다. 그러나 RAII가 대안으로 사용될 수 newdelete라이브 독립적으로 범위의 목적을 위해. 이러한 기술은 힙에 할당 된 오브젝트에 대한 포인터를 핸들 / 관리자 오브젝트 에 배치하는 것으로 구성됩니다 . 후자는 물체 파괴를 담당하는 소멸자를 가지고 있습니다. 이를 통해 액세스하려는 모든 함수에서 오브젝트를 사용할 수 있으며 핸들 오브젝트 의 수명이 종료되면 명시적인 정리없이 오브젝트가 소멸됩니다 .

RAII를 사용하는 C ++ 표준 라이브러리의 예는 std::stringand 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 으로 Scope-Bound Resource Management의 줄임말입니다 .


1
"SBRM"은 나에게 훨씬 더 의미가 있습니다. RAII를 이해한다고 생각했기 때문에이 질문에 왔습니다. 그러나 "Scope-Bound Resource Management"라는 말을 듣고 그 개념을 이해했음을 즉시 알게되었습니다.
JShorthouse

이것이 왜 이것이 질문에 대한 답변으로 표시되지 않았는지 잘 모르겠습니다. @elmiomar
Abdelrahman Shoman (

13

디자인 패턴이 공개C ++ 프로그래밍 서적 은 RAII를 다음과 같이 설명합니다.

  1. 모든 자원 확보
  2. 자원 사용
  3. 자원 해제

어디

  • 리소스는 클래스로 구현되며 모든 포인터에는 주위에 클래스 래퍼가 있습니다 (스마트 포인터 만들기).

  • 자원은 생성자를 호출하여 획득하고 소멸자를 호출하여 내재적으로 (취득 순서의 역순으로) 해제합니다.


1
@Brandin 나는 글을 편집하여 독자들이 정당한 사용을 구성하는 것에 대한 저작권법의 회색 영역에 대해 토론하기보다는 중요한 콘텐츠에 집중할 수 있도록했습니다.
Dennis

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 Way , Toptal 엔지니어 인 Peter Goodspeed-Niklaus는 가비지 컬렉터의 역사를 엿보고 소유권과 차용 개념이 안전 보장을 손상시키지 않으면 서 가비지 컬렉터를 제거하는 데 어떻게 도움이되는지 설명합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.