상세 컨텐츠

본문 제목

RAII idiom

컴퓨터 언어/디자인패턴

by cepiloth 2021. 5. 7. 14:27

본문

728x90
반응형

 

RAII 자체는 “Resource Acquisition Is Initialization”을 뜻하지만, 사실 이 기법의 핵심은 초기화가 아니라 파괴이다.

 용어의 설명이 모호하다. 자원을 안전하게 사용하기 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다. C++에서는 힙(Heap) 영역에 할당된 자원을 명시적으로 해제하지 않으면 스택(Stack) 영역에 할당한 자원은 자신의 스코프(Scope)가 끝나면 메모리가 해제되며 소멸자(Destructor)가 불린다는 원리를 이용한 것이다. - 스택 되감기(Stack Unwinding)

 

애매한 용어

https://qastack.kr/programming/395123/raii-and-smart-pointers-in-c

 

C ++의 RAII 및 스마트 포인터

 

qastack.kr

 

 해외의 개발자들로 RAII라는 용어에 대해서 모호한 점을 지적한다. 필자도 생각하기에 SBRM(Scope Bound Resource Management)이라는 이름이 이해하기 하기 더 좋다.  RAII 목적은 블록의 시작 부분에 리소스를 할당하고 스코프 블록이 종료될 때 리소스를 해제하는 것이 목적이니...

용어의 설명은 여기까지 하고 코드로 확인해보자. 아래와 같이 file을 오픈한다고 했을 때

std::FILE* file = std::fopen("./test.txt", "w+");
// TODO :
std::fclose(file);

 

메모리 할당(malloc)이 실패하게 되면 기존에 파일 오픈한 file 인스턴스가 해제되지 않는다.

std::FILE* file = std::fopen("./test.txt", "w+");
	
void* buff = std::malloc(4);
if (buff == nullptr) {
	return -1;
}
	
std::fclose(file);

 

메모리 할당(malloc)이 실패한다고 해서 try catch를 사용하여 exception을 사용하거나 예외처리를 할 수 있다.

if (buff == nullptr) {
   std::fclose(file);
   return -1;
}

 간단한 코드에서는 위와 같이 에러 처리를 해줘도 상관은 없습니다. 하지만 저런 식으로 처리할 때는 많은 사람들이 참여하거나 공동 작업을 하는 프로젝트에서는 설계에 대한 명세를 명확히 알아야 하며, 선 개발자의 의도를 파악하지 못하면 커뮤니케이션 문제가 발생할 수 있습니다.

이런 경우에는  RAII 을 적용 시에 효과를 볼 수 있습니다.

#include <iostream>
#include <string>
using namespace std;

/*
* RAII 이 적용된 class 
*/

class FileRAII {
public:
	FileRAII(const string& path, const string& mode) {
		this->file = std::fopen(path.c_str(), mode.c_str());
	}

	// 소멸자에서 instance 를 해제하게함
	~FileRAII() {
		std::fclose(file);
	}

	std::FILE* file;
};

int main(int argc, char** argv)
{
	// stack 에 할당
	FileRAII file("test.txt", "wb+");
	
	void* buff = std::malloc(4);
	if (buff == nullptr) {
		return -1;
	}
	
	return 0;
}

 

디버깅

24 번째 라인을 강제로 할당 실패하게 하고 디버깅해보았습니다. 스택(Stack) 영역에 할당된 FileRAII의 인스턴스가 소멸자를 호출하며 스택 되감기(Stack Unwinding) 하면서 file 멤버 변수를 해제합니다.

 

C++ 표준 라이브러리 스마트 포인터

POCO(Plain Old C++ 개체)에 대한 포인터를 캡슐화하는 데 가장 먼저 스마트 포인터를 사용합니다.

 

unique_ptr : 기본 포인터로 한 명의 소유자만 허용합니다.

 shared_ptr이 필요하다는 점을 확실히 알 경우 POCO의 기본 선택으로 사용합니다. 새 소유자로 이동할 수 있지만 복사하거나 공유할 수 없습니다. 사용하지 않는 auto_ptr을 대체합니다. boost::scoped_ptr과 비교합니다. unique_ptr는 작고 효율적입니다. 크기는 하나의 포인터이며, c + + 표준 라이브러리 컬렉션에서 빠른 삽입 및 검색을 위해 rvalue 참조를 지원합니다.

unique_ptr<plot_src> p(new plot_src); // now, p owns
unique_ptr<plot_src> u(move(p)); // now, u owns, p owns nothing.
unique_ptr<plot_src> v(u); // error, trying to copy u

vector<unique_ptr<plot_src>> pv; 
pv.emplace_back(new plot_src); 
pv.emplace_back(new plot_src);

 

shared_ptr : 참조 횟수가 계산되는 스마트 포인터입니다.

 원시 포인터 하나를 여러 소유자에게 할당하려고 할 경우 사용합니다(예: 컨테이너에서 포인터 복사본을 반환할 때 원본을 유지하고 싶을 경우). 원시 포인터는 모든 shared_ptr 소유자가 범위를 벗어나거나 소유권을 포기할 때까지 삭제되지 않습니다. 크기는 2개의 포인터입니다. 하나는 개체용이고, 다른 하나는 참조 횟수가 포함된 공유 제어 블록용입니다.

shared_ptr<plot_src> p(new plot_src(&fx));
plot1->add(p)->setColor("#00FF00");
plot2->add(p)->setColor("#FF0000");
// if p now goes out of scope, the src won't be freed, as both plot1 and 
// plot2 both still have references. 

 

weak_ptr : shared_ptr과 함께 사용할 수 있는 특별한 경우의 스마트 포인터입니다.

weak_ptr은 하나 이상의 shared_ptr 인스턴스가 소유하는 개체에 대한 액세스를 제공하지만, 참조 수 계산에 참가하지 않습니다. 개체를 관찰하는 동시에 해당 개체를 활성 상태로 유지하지 않으려는 경우 사용합니다. shared_ptr 인스턴스 사이의 순환 참조를 차단하기 위해 필요한 경우도 있습니다.

 

원시데이터의 unique_ptr 적용

class CustomDeleter
{
public:
    void operator()(void* p) const;
};

void CustomDeleter::operator()(void* p) const
{
    free(p);
}

std::unique_ptr<char, memory::BrFree_t> buffer_t((char*)malloc(1024));
char* data = buffer_t.get();

 

std::unique_ptr 에는 custom deleter 사용

람다식 지정 하여 Custom Deleter 활용하는 방법

// 1. functional
std::unique_ptr<foo, std::function<void(foo*)>> foo_ptr1(new foo, [](foo* p)
{
delete p;
});


// 2. decltype
auto deleter = [](foo* p)
{
delete p;
};

std::unique_ptr<foo, decltype(deleter)> foo_ptr2(new foo, deleter);

 

C++ 다른 언어보다 메모리 관리의 책임이 개발자에게 많다. 메모리를 자유롭게 핸들링할 수 있다는 장점도 있지만 그에 비해 현대의 소프트웨어에서 가장 많은 문제가 발생하는 것이 메모리 누수 등의 문제가 대부분이다. 스마트 포인터 및 RAII Idiom을 적극적으로 활용하자.

728x90
반응형

'컴퓨터 언어 > 디자인패턴' 카테고리의 다른 글

코드 네이밍 RawBitmap Data  (0) 2021.05.07
핌플 이디엄 Pimpl idiom  (0) 2021.05.07
싱글턴 패턴 SingleTon pattern  (0) 2021.05.07
빌더 패턴 사용 이유  (0) 2021.05.07
빌더 패턴 Build pattern  (0) 2021.05.07

관련글 더보기

댓글 영역