본문 바로가기
프로그래밍 언어/C++

C++ 11 / 스마트 포인터 unique_ptr

by Nighthom 2023. 2. 15.

1. 스마트 포인터란?

C++에서 메모리 관리를 실수하는 경우를 줄이기 위해 만든 포인터 변수를 담은 객체를 스마트 포인터라고 한다.

 

해당 객체가 필요한 이유는, 포인터는 객체가 아니기 때문에 따로 소멸자가 존재하지 않기 때문이라고 할 수 있다. 포인터를 사용할 경우, 어떠한 메모리를 new로 할당해서 포인터에 담았다면 해당 포인터는 반드시 delete해 주어야 한다. 함수 스코프에서 사라진다고 자동으로 소멸자가 호출되어서 소멸시켜 주지 않는다. 

하지만, 포인터 변수를 따로 객체가 생성될 때 생성하고, 소멸할 때 알아서 지워준다면 해당 객체의 수명이 끝나는 시점(함수 스코프가 닫혀 스택에서 할당 해제 되는 경우 등)에서 알아서 소멸자를 호출해 delete를 호출할 수 있게 해준다! 

 

이러한 객체를 C++ 11부터 unique_ptr, shared_ptr이라는 형태로 제공한다. 

2. unique_ptr 사용법

unique_ptr<type> p_ptr;

unique_ptr은 ->연산자를 오버로딩했기 때문에 포인터를 다루는 것 처럼 사용 가능하다. 예를 들어 객체 type에 어떠한 메서드 insert가 존재한다면 다음과 같이 호출할 수 있다. 

p_ptr->insert("a");

해당 포인터의 주소에 직접 접근하려면 다음과 같이 사용하면 된다.

p_ptr.get();

C++ 14부터 unique_ptr 생성을 쉽게 만들어주는 std::make_unique() 함수를 지원한다.

auto ptr = std::make_unique<A>();

 

3. unique_ptr의 특성

unique_ptr은 따로 복사 생성자 / 대입 연산자가 삭제되어 있다(delete를 통해서 삭제함). 따라서 unique_ptr로 생성한 어떤 객체는 다른 unique_ptr에 담을 수 없다는 특징이 있다.

unique_ptr<type> ptr1(new type());
unique_ptr<type> ptr2 = ptr1;				// 문법 오류!

즉, 해당 객체가 해당 포인터를 소유한 유일한 객체라는 것을 보장함으로써 double free 버그를 방지한다. (만약 두 객체에 똑같은 포인터가 담기게 된다면 하나의 소멸자가 호출될 때 메모리가 delete되고, 다음 객체의 소멸자가 호출될 때 이미 해제된 메모리를 다시한번 delete를 시도함으로써 런타임 오류가 발생한다.) 

 

하지만 move를 활용해서 소유권을 이동시킬 수는 있다. 

unique_ptr<type> ptr1(new type());				// nullptr을 가리키게 됨
unique_ptr<type> ptr2 = std::move(ptr1);			// ptr1의 포인터를 가리키게 됨

move(ptr1)을 활용해서 ptr2이 가리키는 포인터를 ptr1의 포인터로 옮겨줄 수 있다. 하지만 옮겨지게 되면 ptr1에는 nullptr이 담기게 된다. 이것 또한 double free 버그를 방지하기 위해서 이러하구나 하고 이해하면 좋다. 이런 식으로 소유권 이동을 시켰을 때 nullptr을 가리키는 ptr1을 댕글링 포인터라고 한다. nullptr에 접근해서 어떠한 연산을 처리한다면 런타임 오류가 발생하므로, 이러한 소유권 이전은 이후에 ptr1에 절대 접근하지 않을 상황에서만 사용하면 좋다.

4. unique_ptr을 파라미터로 전달하기

unique_ptr을 파라미터로 전달할 때는 항상 get() 메서드를 활용해서 전달하는 편이 좋다. 그 이유는 unique_ptr을 직접 전달할 경우 함수가 스택을 정리할 때 delete되기 때문에, 메모리에서 해제되기 때문이다. 

void do_something(std::unique_ptr<A>& ptr); 	// 잘못된 사용법!
void do_something(A* ptr);			// O

int main() {
    unique_ptr<A> a(new A);
    do_something(a.get());
}

5. unique_ptr을 컨테이너에 넣는 방법

std::vector<unique_ptr<A>> vec;
auto a = std::make_unique<A>();

vec.push(a);				// 문법 오류!	
vec.push_back(std::move(a));		// 올바른 방식

unique_ptr에는 복사 생성자가 정의되어 있지 않는다. 따라서 push_back과 같은 메서드로 넣을 때는 어떠한 대상을 복사하게 되므로 오류가 발생하게 된다. push_back의 rvalue 참조 버전은 이동 생성자, 이동 대입 연산을 수행하게 되므로 이러한 컴파일 오류가 나지 않게 된다. 따라서 move()를 통해서 unique_ptr을 rvalue로 전환해 주어야 한다. 

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ / std::function  (0) 2023.02.16
C++ 11 / 스마트 포인터 shared_ptr, weak_ptr  (0) 2023.02.15
C++ 11 / 보편적 참조(universal reference)와 forward  (0) 2023.02.15
C++ 11 / move함수  (0) 2023.02.15
C++ 11 / Rvalue 참조  (0) 2023.02.15

댓글