용어의 설명이 모호하다. 자원을 안전하게 사용하기 위해 객체가 쓰이는 스코프를 벗어나면 자원을 해제해주는 기법이다. C++에서는 힙(Heap) 영역에 할당된 자원을 명시적으로 해제하지 않으면 스택(Stack) 영역에 할당한 자원은 자신의 스코프(Scope)가 끝나면 메모리가 해제되며 소멸자(Destructor)가 불린다는 원리를 이용한 것이다. - 스택 되감기(Stack Unwinding)
https://qastack.kr/programming/395123/raii-and-smart-pointers-in-c
해외의 개발자들로 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 멤버 변수를 해제합니다.
POCO(Plain Old C++ 개체)에 대한 포인터를 캡슐화하는 데 가장 먼저 스마트 포인터를 사용합니다.
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 소유자가 범위를 벗어나거나 소유권을 포기할 때까지 삭제되지 않습니다. 크기는 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 인스턴스가 소유하는 개체에 대한 액세스를 제공하지만, 참조 수 계산에 참가하지 않습니다. 개체를 관찰하는 동시에 해당 개체를 활성 상태로 유지하지 않으려는 경우 사용합니다. shared_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();
람다식 지정 하여 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을 적극적으로 활용하자.
코드 네이밍 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 |
댓글 영역