[C++] 스마트 포인터: unique_ptr
이전 포스트: "자원 관리와 스마트 포인터"
https://m.blog.naver.com/sssang97/221805519374
unique_ptr은 이름 그대로 유일성(unique)를 갖는 포인터다. 이 포인터의 포인터값은 복사될 수 없고, 하나의 unique_ptr 객체만이 하나의 포인터값을 쥐고 있을 수 있다.
이런걸 '소유권'이라고 부르곤 한다.
근데 이렇게 소유권을 부여하는 것으로 얻을 수 있는 게 뭘까?
하나의 unique_ptr이 과감하게 할당을 해제해도, 그로 인해 dangling 포인터(유효하지 않은 포인터)가 발생하지 않을 거라는 보장이 생기는 것이다!
한놈만 쥐고 있는거니까.
그리고 unique_ptr은 객체가 영역(scope)을 벗어나 자신의 소멸자가 호출될 때 자동으로 할당을 해제한다.
근데 영역을 넘나들게 하고 싶다면 어떨까? 그냥 영역에 갇힌 채로만 사용해야 하는 건가?
당연히 아니다. 그냥 C++11의 move semantic을 사용해 유일성을 보존한 채로 객체를 이동시키면 된다.
백문불여일견이라, 코드부터 한번 봐보자.
일단 생성과 소멸을 확인하기 위해, 전용 래퍼 타입을 구현해봤다. 별건없다.


그럼 이제 저걸로 스마트포인터를 써보자. unique_ptr은 템플릿 클래스다. 포인터의 원본타입을 템플릿 인자로 받는다.
그리고 생성자론 동적할당된 포인터값을 받는다.
게다가 *, -> 등의 연산자가 정의되어있어 그냥 포인터와 마찬가지로 사용할 수 있다.

잘 돌아가는걸 볼 수 있다.
그리고 동적할당된 Int의 소멸자가 자동으로 호출됐다. main 함수가 종료되며 unique_ptr의 소멸자가 작동, 객체를 정리한 것이다.
복사 금지
그리고 앞서 언급했듯이, unique_ptr은 유일성을 보장한다. 따라서 복사는 불가능하다.

복사생성자와 복사할당연산자가 전부 삭제된 함수라 그렇다.
unique_ptr의 값을 동적으로 여기저기 넘기고 싶다면, 그냥 참조로 넘기거나 이동시켜야 한다.

자세한 것은 무브시맨틱 포스트들을 참조하길 바란다.
https://m.blog.naver.com/sssang97/221472346738
복사의 예외?
unique_ptr의 복사는 원천적으로 금지되어있지만, 한가지 예외가 존재한다.
바로 함수의 반환값 최적화(Return Value Optimization)로 날라온 값은 받을 수 있다는 것이다.
반환값 최적화가 수행되는 함수의 반환값은, 사실상 이동으로 넘어오기 때문이다.

그래서 이럴때는 굳이 반환타입을 &&로 해놓고 이동을 사용할 필요가 없다.
포인터의 생존 확인
unique_ptr는 이동시키면 원본 포인터 객체가 죽는다. 하지만 내부 객체가 날라가는 거지, 사용이 불가능해지는 건 아니라, 유효성을 체크해야 할 수도 있다.
포인터 객체의 생사 확인은 bool 캐스팅으로 해결할 수 있다.
이런식으로.


성능?
unique_ptr의 성능은 일반 포인터와 거의 같다고 보면 된다.
그냥 소멸자에서 delete해주는게 다라서,
추가로 메모리를 먹는 것도 없고... 그냥 가볍게 사용해도 무난하다.
좀 더 편리한 생성: make_unique
위에선 스마트포인터를 생성할때, 생성자 인자로 직접 new를 해서 보내줬었다.
근데 꼭 그럴 필요가 있는걸까?
게다가 저러면 타입 표현이 중복된다.
unique_ptr<Int> p(new Int(99));
제너릭 인자로 Int 하나, 또 new할때 Int 하나.
보기에 썩 좋지는 않다.
make_unique는 이러한 표현을 좀더 간소화해주는 생성용 함수다.
제너릭 인자로 타입을 써주고, 함수 인자에는 생성자의 인자들만 가변인자로 보내주면 된다.
그러면 알아서 내부적으로 new 생성해준다.

아 근데 이건 C++14부터 제공된다.
이외에도 여러가지 기능이 있으니 자세한 사항은 문서를 참조하길 바란다.
unique_ptr 규격 참조
https://en.cppreference.com/w/cpp/memory/unique_ptr