본문 바로가기

C++

[C++] 스마트 포인터, unique_ptr

C++의 포인터는 new로 할당을 하면, 반드시 delete로 해제해야 메모리 누수가 발생하지 않는다. 하지만 방대한 프로그램에서 프로그래머가 하나하나 빠지지 않고 할당된 포인터를 해제하는 것은 어려운 일이다. 그래서 만들어진 것이 스마트 포인터이다. 스마트 포인터는 포인터 객체이다. 포인터와 달리 객체는 함수가 종료될 때 자동으로 자신의 소멸자를 호출한다. 그래서 소멸자에 delete를 넣으면 메모리를 해제할 수 있고 이를 포인터에 적용한 것이다.

 

Data* data = new Data();
Date* data2 = data;

// data 의 입장 : 사용 다 했으니 소멸시켜야지.
delete data;

// ...

// data2 의 입장 : 나도 사용 다 했으니 소멸시켜야지
delete data2; // 출처 : https://modoocode.com/

 

일반 포인터를 사용할 때 발생하는 또 다른 문제점은 이미 해제한 메모리를 이중으로 해제하는 것이다. unique_ptr은 생성한 포인터 객체의 소유권을 부여해서 메모리를 이중으로 참조할 수 없게 한다. 그렇다면 unique_ptr을 복사하는 것은 가능할까? 

 

 std::unique_ptr<A> pa(new A());

  // pb 도 객체를 가리키게 할 수 있을까?
  std::unique_ptr<A> pb = pa;  // 출처 : https://modoocode.com/

 

unique_ptr을 복사하는 것은 불가능하다. 복사 생성자 자체가 삭제되어 있다. unique_ptr은 포인터 객체를 유일하게 소유해야 하기 때문이고 unique_ptr들이 소멸될 때 객체를 이중으로 delete하는 문제가 발생하기 때문이다.

 

  std::unique_ptr<A> pa(new A());
  
  // pb 에 소유권을 이전.
  std::unique_ptr<A> pb = std::move(pa);

 

복사하는 것은 불가능하지만 소유권을 이전하는 것을 이용한다. std::move를 이용하며 pb가 pa가 가리키던 것을 가리키게 되고 pa는 아무것도 가리키지 않는 댕글링 포인터가 된다. 

 

// 올바르지 않은 전달 방식
void do_something(std::unique_ptr<A>& ptr) { ptr->do_sth(3); }

int main() {
  std::unique_ptr<A> pa(new A());
  do_something(pa);
}

 

위 코드는 unique_ptr을 함수 인자로 전달한 것이다. unique_ptr을 함수 인자로 전달할 시 pa가 유일하게 소유하고 있던 객체를 do_something 함수에서 ptr이라는 레퍼런스를 통해 소유할 수 있게 된다. 그러면 유일한 소유권을 가지는 unique_ptr의 원칙에 위배된다. 그래서 함수 인자로 unique_ptr을 전달할 때 다음과 같은 방법을 사용한다.

 

void do_something(A* ptr) { ptr->do_sth(3); }

int main() {
  std::unique_ptr<A> pa(new A());
  do_something(pa.get());
}

 

get함수를 통해 unique_ptr의 객체의 주소값을 구할 수 있다. 그것을 함수 인자로 전달함으로써 객체 주소값을 직접 전달하고 unique_ptr의 소유권도 침범하지 않는다. 

 

std::unique_ptr<Foo> ptr(new Foo(3, 5));

auto ptr = std::make_unique<Foo>(3, 5); //make_unique사용

 

C++14의 make_unique 함수를 통해 간단히 만들 수 있다.