웬디의 기묘한 이야기

글 작성자: WENDYS
반응형

RAII


RAII(Resource Acquisition Is Initialization)은 유명한 design pattern 중의 하나로 C++ 언어의 창시자인 Bjarne Stroustrup에 의해 제안되었다.

RAII 패턴은 C++ 같이 개발자가 직접 resource 관리를 해주어야 하는 언어에서 leak 을 방지하기 위한 중요한 기법으로

해당 리소스의 사용 scope이 끝날 경우에 자동으로 해제를 해주며 exception이 발생하거나 하는 경우에도 획득한 자원이 해제됨을 보장하여

안전한 코드를 작성할 수 있다.

 

resource라 하면 memory가 될 수도 있고 handle 이 될 수도 있다.

즉, 모든 Leak이 일어날 수 있는 resource에 대해서 안전하게 처리할 수 있는 패턴이다.


그 중 Lock Object의 경우엔 Dead Lock 까지도 유발할 수 있는 치명적인 문제가 발생할 수 있기때문에 해당 패턴이 필수적으로 사용된다.






memory example


STL에는 빈번히 일어나는 메모리 할당 해제를 RAII로 지원하는 std::auto_ptr 이 존재한다.

auto_ptr(https://msdn.microsoft.com/ko-kr/library/ew3fk483.aspx)


사용법은 매우 간단하다.


#include <memory>

void main(void) {

    // 1
    {
        std::auto_ptr<char> buffer(new char[100]);
    }

    // 2
    {
        auto auto_close_handle = [](
            __in HANDLE* handle
            ) {
            if (handle) {
                CloseHandle(*handle);
                delete handle;
            }
        };

        std::unique_ptr<HANDLE, decltype(auto_close_handle)> process_handle(new HANDLE, auto_close_handle);

        const DWORD process_id = 12345678;

        *process_handle.get() = OpenProcess(
            PROCESS_QUERY_INFORMATION,
            FALSE,
            process_id
            );
    }
}


1번의 경우엔 scope가 끝나는 시점에 메모리가  해제되어 Leak을 걱정하지 않아도 되며,

2번의 경우 HANDLE Leak을 걱정하지 않을 수 있도록 delete funtor 를 지정해주었다


자주 사용하는 Resource의 경우엔 위와 같이 쓰기보단 미리 만들어놓고 여기저기 땡겨쓰는게 좋을 것 같다.


* auto_ptr은 주의해서 사용해야 하는 함수입니다. (컨테이너 등에 넣지 못하는 분제 발생)

컨테이너에 넣지 못하는 이유는 일반적인 복사 동작과는 다른 동작을 하기 때문이다.

스마트 포인터 객체를 값에 의한 전달로 넘겨주게 되면 객체 복사가 일어나게 되는데, 이때 스마트 포인터가 가진 자원도 같이 복사가 일어나는 게 아니라 자원에 대한 소유권(ownership)을 넘겨주게 된다.

다른 말로 소멸식 복사(destructive copy)라고도 하는데, A에서 B로 복사하게 되면 A가 가지고 있던 자원이 복사가 아니라 전달되게 된다.

(이를 해소하기 위해 나온게 unique_ptr 이다. unique_ptr은 복사생성자와 대입연산은 지원하지 않으며, std::move를 위한 move 생성자/move 대입 연산자만 존재한다.)


reference count 를 지원하는 std::shared_ptr 의 사용을 추천합니다. 






auto lock example


위키(https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 에도 나와있는 RAII 예 입니다.


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

void write_to_file (const std::string & message) {
    // mutex to protect file access (shared across threads)
    static std::mutex mutex;

    // lock mutex before accessing file
    std::lock_guard<std::mutex> lock(mutex);

    // try to open file
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");
    
    // write message to file
    file << message << std::endl;
    
    // file will be closed 1st when leaving scope (regardless of exception)
    // mutex will be unlocked 2nd (from lock destructor) when leaving
    // scope (regardless of exception)
}


auto lock 의 경우 여러 방법으로 구현해서 사용하며, STL에서도 다음과 같이 지원 합니다.


앞의 코드는 예외에도 안전하다.

C++이 Scope가 종료될 때 Stack에 있는 모든 객체의 소멸자를 호출하기 때문이다. 

이는 stack unwinding이라고 불린다. lock과 file 객체의 소멸자는 함수가 종료될 때 항상 호출되는데 Exception이 발생하든 발생하지 않든 소멸자는 항상 호출된다.



지금까지 봐온 내용으로 보아 RAII를 구현하기 위해선 조건이 있다.

C++ 언어 같이 class에 Destructor를 만들수 있고 object 를 stack 에 생성하는 것을 허용하는 언어에서만 사용가능하다.


단순하지만 아주 효율적이며, 이젠 일반적으로 사용하게 된 패턴이다.


잘못된 내용이 있는 경우 언제든지 지적해주세요!!




반응형