C++/More Effective C++

[Modern Effective C++] Chap 4. Smart Pointers

라부송 2020. 3. 2. 23:01

Item 18: Use std::unique_ptr for exclusive-ownership resource management.

소유권 독점 자원의 관리에는 std::unique_ptr을 사용하라

 

unique_ptr : 독점 소유권 의미론을 가진 자원의 관리를 위한, 작고 빠른 이동 전용 스마트 포인터

 

shard_ptr의 단점

1. 참조 계수를 관리하기 위해 나타나는 overhead

2. raw pointer로 복귀 불가

 

+ shared_ptr 에서 unique_ptr 변환은 불가능하지만, 그 반대는 가능하다.

+ shared_ptr은 raw pointer 메모리의 2배를 차지하지만, unique_ptr 은 raw와 정확히 같은 메모리를 할당받는다.

-> 소유권 독점 여부가 정해져있다면, 굳이 shared_ptr을 쓸 이유가 없다.

 

주로 팩토리 패턴에서 쓰인다.

class Investment { ... };
 
class Stock:
    public Investment { ... };
 
class Bond:
    public Investment { ... };
 
class RealEstate:
    public Investment { ... };
 
// 팩터리 함수는 다음과 같이 만들 수 있을 것이다.
 
template <typename... Ts>            // 주어진 인수들로 생성한
std::unique_ptr<Investment>            // 객체를 가리키는
makeInvestment(Ts&&... params);        // std::unique_ptr를 돌려줌
 
// 다음은 이 팩터리 함수를 사용하는 예이다.
 
{
    ...
    auto pInvestment =                // pInvestment의 타입은
        makeInvestment( 인수들 );        // std::unique_ptr<Investment>
    
    ...
} 

 

Item 19: Use std::shared_ptr for shared-ownership resource management.

소유권 공유 자원의 관리에는 std::shared_ptr을 사용하라

 

분명 shared_ptr 은 unique_ptr 보다 유연하다.

하지만 위에서 말한 단점들은 사라지지 않는다.

 

1. shard_ptr의 크기는 생 포인터의 두 배이다. (비참조의 경우에도 마찬가지)

2. 참조 카운트를 담을 메모리를 반드시 동적으로 할당해야 한다.

3. 참조 카운트의 증가와 감소가 반드시 원자적 연산이어야 한다. (상호 배제 때문)

   - 참조 카운트는 대체로 비원자적 연산보다 느림

 

shared_ptr의 장점은 없을까?

- 소유권을 공유하는 경우엔 필수적이고,

- 동적 할당 자원의 수명이 자동으로 관리된다는 이득이 생긴다.

 

그러나 독점 소유권을 쓰는 경우라면 unique_ptr 이 당연히 더 나은 선택이다.

 

 

 

Item 20: Use std::weak_ptr for std::shared_ptrlike pointers that can dangle.

std::shared_ptr 처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr를 사용하라

 

weak_ptr : shared_ptr을 이용해서 생성되나, 참조 카운트를 하지 않는다.

 

캐싱, 관찰자 목록 등등 만료 여부를 판단해야 할 때 쓰인다.

auto spw =                            // spw가 생성된 후, 피지칭
    std::make_shared<Widget>();        // Widget의 참조 카운트(이하
                                    // 간단히 카운트)는 1이다
...
 
std::weak_ptr<Widget> wpw(spw);        // wpw는 spw와 같은 Widget을
                                    // 가리킨다; 카운트는 여전히 1이다.
...
 
spw = nullptr;                        // 카운트가 0이 되고 Widget이
                                    // 파괴된다; 이제 wpw는
                                    // 대상을 잃은 상태이다.
 
 
//-----------------------------------------------------------------------------
// std::weak_ptr가 만료되었는지 알고 싶으면,
// expired 멤버 함수가 돌려주는 값을
// 점검하면 된다.
 
if (wpw.expired()) ...                // wpw가 객체를
                                    // 가리키지 않으면...

 

Item 21: Prefer std::make_unique and std::make_shared to direct use of new.

new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라.

 

new의 직접 사용에 비해, make 함수를 사용하면 소스 코드 중복의 여지가 없어지고, 예외 안전성이 향상된다.

기존 new에 비해 더 작고 빠른 코드가 산출된다.

 

아래와 같이 인수를 반영해 객체의 생성자를 호출하여 그걸 가리키는 shared_ptr 를 반환한다.

auto a = std::make_shared<Example>(1, 2);
auto b = std::make_shared<Example>(std::initializer_list<int>{1, 2});

 

 

 

Item 22:When using the Pimpl Idiom, define special member functions in the implementation file.

Pimpl 관용구를 사용할 때에는 특수 멤버 함수들을 구현 파일에서 정의하라

 

Pimpl 자체에 대한 설명은 아래 블로그가 잘 정리해주었다.

https://grayt.tistory.com/66

 

[C++ 디자인 패턴] pimpl idiom (pimpl 관용구)

pimpl idiom 컴파일 의존성을 줄이기 위한 하나의 설계 기법이다. 많이 쓰여지는 기법 중 하니이기 때문에 거의 패턴으로 굳어져 있다. include가 필요한 사용자 정의 타입의 멤버 변수들을 해당 멤버 변수들을 포..

grayt.tistory.com

Pimpl 패턴은 컴파일 의존성을 줄이는데 크게 기여한다.

그러나 unique_ptr 타입의 Pimpl 포인터를 사용할 때는 특수한 문제에 직면한다.

 

std::unique_ptr은 deletor type이 template 인자로 받아들여져 type에 포함된다. 

그 템플릿 인자의 기본값은 delete 구문이며, 이는 객체의 소멸자를 호출할 것이다.

 

따라서 불완전한 타입 (컴파일 중 unique_ptr 선언 시점에서 정의되지 않은 타입) 은 unique_ptr와 함께 쓸 수 없다.

shared_ptr은 커스텀 삭제자를 지정해도 객체의 영향을 끼치지 않기 때문에 이에 해당하지 않는다.

 

그래서 제시한게 Pimpl (특수 멤버 함수들을 클래스 헤더에 선언 후, 구현 파일에서 따로 구현) 이다.

그러나 다른 해결책을 제시한 사람이 있다.

 

http://taeguk2.blogspot.com/2017/04/effective-modern-c-chapter-4-smart.html