[Modern C++] unique_ptr 사용법 상세

Program Lang./C++ 2019. 9. 20. 16:14

1. unique_ptr 이란?

 

unique_ptr은 스마트포인터로 creational design pattern에서 많이 사용되는 포인터이다.

unique_ptr을 완벽히 이해하기 위해서는 move에 대한 고찰도 필요하다.

여기서는 복잡한 개념보다는 실제 사용법 위주로 정리하였다.

 

2. 사용예제

 

실제 개발을 진행하면서 명확한 사용법에서 해메는 경우가 종종있다. 이런 측면에서 보통 많이

적용되는 사용법 위주로 정리한 코드 예제들이다.

특히 unique_ptr을 함수의 파라미터로 사용할 때, 값/참조/rvalue의 선택지가 있는데

어떤 것을 사용해야 할지가 가장 햇갈리는 부분이였다.

간단히 사용법 측면에서 정리하면 다음과 같이 정리할 수 있을 것 같다.

 

  • 받는쪽(함수)로 완전히 자원을 넘겨야 할 경우
    • 함수파라미터를 값이나 rvalue로 선언한다.
    • rvalue로 선언할 경우, 받는쪽에서 move를 통해서 자원을 가져가야 자원이동이 완료된다.
  • 전달한 쪽에서도 계속 자원을 사용해야 할 경우
    • 함수파라미터를 참조로 선언해야 한다.

 

 

4. Pimpl Idiom with unique_ptr

 

unique_ptr과 함께 숙지해 놓으면 좋은 예가 c++ Idiom 중에서 Pimpl이다.

Pimpl은 컴파일 시간을 줄이기 위한 방법으로 Effective Modern C++ 22장에 정리되어 있다.

그 책을 예제로 정리해 놓는다.

아래 코드는 user define Gadget class가 widget.h에 포함되어 있다. widget.h 파일을 많은 cpp 파일에서

include 시킨다고 가정하면, 사용자가 개발 중에 Gadget을 빈번하게 수정한다면 widget.h를 include하는

모든 파일은 컴파일 대상이되어 빌드 시간이 오래걸린다. (컴파일러는 header 파일만 바라본다)

 

<컴파일 시간이 오래걸리는 코드 원본>

 

아래 코드는 위의 컴파일 시간 문제를 C++98 버전으로 Pimpl을 구현한 코드이다.

  1. struct Impl에 사용자 변수를 package한다.
  2. Impl *pImpl raw pointer로 할당받는다. Gadet의 변경은 이제 컴파일 시간에 영향을 주지 않는다.
  3. 실제 코드는 cpp 구현부에 존재한다.
  4. raw pointer의 사용으로 사용자 정의 생성자/소멸자를 만들어줘야 한다.

<C++ 98 Pimpl 구현 코드>

 

이제 Pimpl에 c++11 unique_ptr 사용한다면 컴파일러가 자동으로 소멸자를 생성할 것으로 기대할 수 있다.

하지만 사용자가 생성자를 생성해야 한다. 그 이유는 책에 잘 설명되어 있다.

결론은 shared_ptr와 다르게 unique_ptr은 불완전 type(Impl)에 대해서 소멸자를 자동생성할 수 없다.

따라서 Pimpl처럼 불완전 타입을 unique_ptr에 적용하려면 사용자가 소멸자를 선언 및 정의해 줘야 한다.

소멸자를 사용자 재정의함에 따라서 move/copy 모든 생성자까지 사용자가 생성해줘야 한다.

special member function(https://chipmaker.tistory.com/entry/Modern-C-Summary-of-special-member-function-generation)을 참조하면 상세하게 정리되어 있다.

  1. C++98과 동일하다.
  2. raw pointer에서 smart pointer로 바꾸었다.
  3. header 파일에는 생성자를 선언해야하고, 구현부(cpp)에는 정의가 필요
  4. 생성자와 동일 (unique_ptr을 사용하는 효과가 전혀없다)
  5. move 사용자 재정의로 copy operation(constructor, assignment operator)도 사용자 정의해야 한다.
  6. 소멸자 사용자 재정의로 move operation(constructor, assignment operator)도 사용자 정의해야 한다.

C++98에서 unique_ptr을 사용하면 사용자가 해주어야 할 일이 줄어야 하는데, 차이가 없다.

raw pointer의 자원 관리 문제를 smart pointer를 통해서 Pimpl에서 확장하려면

결국 모든 생성자와 소멸자를 사용자가 해 주어야 한다점이 핵심이다.

물론 compiler가 생성하는 코드와 동일한 코드라면 모두 예제 코드처럼 default로 처리하면 된다.

 

<C++11 Pimpl 구현 코드>

 

5. 정리

 

cppreference에도 사용법은 잘 정리되어 있지만, 바로 보기 힘들어서 여기서 사용법만 재 정리하였다.

unique_ptr을 Pimpl Idiom에 적용할 때, 컴파일 에러상황과 고려해야 할 사항을 정리하였다.

물론 책에도 있지만 네가 다음에 보기 편하게 정리하였다.

Pimpl Idiom은 실제 unique_ptr과 연관성이 높으며, shared_ptr에 적용 예는 드물다.

shared resource에 대해서 Pimpl Idiom을 shared_ptr로 구현한다면, unique_ptr처럼 복잡하지 않다.

단순히 아래 코드처럼 생성자만 사용자가 생성해주면 된다.

소멸자를 사용자가 재정의하지 않아서 나머지 special member function은 컴파일러 모두 자동생성하기

때문이다.   

 

: