[C++] EMCP Item 19: 자원에 대한 공유 소유권을 위해서는 shared_ptr을 사용하라
reference count의 performance implications
(참조 횟수 관리는 다음과 같은 성능에 영향)
1) raw pointer의 2배 크기
referencing mechanism을 처리하기 때문
2) reference count 용 memory(Contorl block)는 동작 할당됨
reference count는 객체에 associated
reference count는 동적으로 할당되는 data에 저장됨
3) reference counting은 atomic 이어야 함
unique_ptr과 달리 shared_ptr은 삭제자의 지정 방식이 다름
auto loggingDel = [](Widget *pw) // custom deleter (as in Item 18)
{
makeLogEntry(pw);
delete pw;
};
// deleter type is part of ptr type
std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);
// deleter type is not part of ptr type
std::shared_ptr<Widget> spw(new Widget, loggingDel);
shared_ptr은 서로 다른 custom deleter를 지녀도 동일 객체로서 관리(사용)됨
auto customDeleter1 = [](Widget *pw) { … }; // custom deleters
auto customDeleter2 = [](Widget *pw) { … };
std::shared_ptr<Widget> pw1(new Widget, customDeleter1);
std::shared_ptr<Widget> pw2(new Widget, customDeleter2);
std::vector<std::shared_ptr<Widget>> vpw{ pw1, pw2 };
control block
shared_ptr 객체는 reference count 용 pointer를 지님
control block에 대한 pointer임
(객체 별 control block이 필요)
control block은
custom deleter의 copy 역시 지님
custom allocator 역시 가능
secondary reference count인 weak count 도 지님
std::shared_ptr<T>
+-----------------+ +----------+
| Ptr to T | ----> | T Object |
+-----------------+ +----------+
| Ptr to Control |
| Block | --+ Control block
+-----------------+ | +-------------------+
+-->| Referece Count |
+-------------------+
| Weak Count |
+-------------------+
| Otehr data |
| e.g., custom |
| deleter/allocator |
| etc. |
+-------------------+
Contrl block은
최초 shared_ptr의 생성 시 set up 됨
control block의 생성
1) make_shard 시 생성
2) shared_ptr에 raw pointer 대입 시 생성
make_shred
기본 생성자, 소멸자 사용 시 사용 가능
control block의 생성에 대한 규칙
1) make_shared는 항상 control block을 생성
2) raw / 고유 소유권 포인터(unique_ptr/auto_ptr)에서 shared_ptr 생성 시
control block 생성
control block을 생성하지 않는 경우
이미 control block을 지닌 객체로부터 shared_ptr 생성은,
이미 생성된 shared_ptr 혹은 weak_ptr을 생성자 argument로 넘기면 됨
raw pointer가 아니어야 함
단, 이 때는 새로운 control block을 생성하지 않음
raw pointer를 넘길 수 있게 되면,
여러 contrl block이 가능해 져서 예상치 못한 오류를 발생
하나의 raw pointer에서 여러 개의 shared_ptr을 생성하면, 객체에 여러 개의 control block이 만들어지기에 이상 동작이 발생할 수 있음
bad ex.
auto pw = new Widget;
std::shared_ptr<Widget> spw1(pw, loggingDel); // create a control block
std::shared_ptr<Widget> spw2(pw, loggingDel); // create a control block
raw pointer인 pw에 직접 할당하는 방식은 나쁘다.
철학에 위배
위 코드에서 *pw는 2개의 reference count를 지님
결국 2회 제거를 시도할 것임 (2번째 desctuction에서 undefined behavior 발생)
shared_ptr 사용 주의점
1) shared_ptr에는 raw pointer를 넘기면 안 됨
대신, make_shared를 사용
custom deleter의 사용 시 make_shared 사용 불가
만약 raw pointer를 사용해야만 한다면, 중간에 pointer 변수 사용 없이
바로 pass 하는 것이 좋음
std::shared_ptr<Widget> spw1(new Widget, loggingDel);
std::shared_ptr<Widget> spw2(spw1);
shared_ptr에 shared_ptr을 치환하면 동일 control block을 사용
2) 객체 내에서 this를 shared_ptr 생성에 넘기지 말아야 함
std::vector<std::shared_ptr<Widget>> processedWidgets;
class Widget {
public:
…
void process();
…
};
void Widget::process()
{
…
processedWidgets.emplace_back(this); // <-- raw pointer!!
}
this에 대해 또다른 shared_ptr이 생성되고 processedWidgets에 들어감
this를 통해 중복 control block 생성을 막는 방법
std::enable_shared_from_this 사용
this pointer로부터 shared_ptr을 안전하게 생성하려면,
class Widget: public std::enable_shared_from_this<Widget> {
public:
...
void process();
};
위의 pattern을 CRTP (Curiously Recurring Template Pattern)이라고 함
enable_shared_from_this의 member function인 shared_from_this를
사용해서 control block을 복제하지 않음
void Widget::process() {
...
processedWidgets.emplace_back(shared_from_this())
}
Raw 객체가 shared_ptr로 치환 시 새로운 control block을 안 만드는 방법
enable_shared_from_this을 상속 받음
class clasname : public std::enable_shared_from_this<classname> {
...
};
enable_shared_from_this는
shared_from_this() member function을 지님
이를 통해 shared_ptr을 생성함 (control block을 생성하지는 않음)
shared_from_this()는
내부적으로 현재 객체의 control block을 찾고
현재 control block을 참조하는 shared_ptr을 생성함
client로부터 shared_from_this를 호출하는 member 함수의 호출을 막기 위해서
enable_shared_from_this를 상속받는 객체는 종종 생성자를 private에 선언
대신 객체의 생성은 factory function을 사용
class Widget: public std::enable_shared_from_this<Widget> {
public:
...
template<typename... Ts>
static std::shared_ptr<Widget> create(Ts&&... params);
...
void process();
..
private:
... // ctors
};
control block's cost
1) 관리 비용
대체로 몇 word 정도임 (custom deleter 사용 시 좀 더 커질 수 있음)
control block이 동적 할당되고 살제자와 할당자의 크기는 얼마든지 가능하고,
가상 함수 mechanism이 사용되고, 참조 횟수가 원자적으로 조작됨
그러나 이 cost는 크렇게 크지 않음
대신, 자원의 수명이 자동으로 관리된다는 이점을 얻게 됨
2) 배열로 처리가 불가능
shared_ptr은 단일 객체 pointer만 염두해 두고 설계되었음
std::shared_ptr<T[]>와 같은 연산이 없음
operator[]가 없기에 색인으로 배열 원소에 접근하려면 포인터 산술에 기초한 어색한 표현식을 도원해야 함
control block은,
기본적으로 a few words in size (three words in size)
여기에 custom deleters and allocators들이 붙으면 커짐
control block도 상속과 virtual function을 사용,
즉, virtual functon에 대한 cost가 존재
그래도 shared_ptr은 기능에 비해 resonalble cost임
Exclusive ownership이 필요시에는 unique_ptr이 휠씬 좋은 성택
unique_ptr은 raw pointer에 성능과 차이가 거의 없음
shared_ptr은 unique_ptr에서 생성됨 (upgrade가 용이함)
단, 반대는 쉽지 않음
shared_ptr에서는 unique_ptr을 얻을 수 없음
shared_ptr의 단점
array에 사용할 수 없음
다음을 제공하지 않음
shared_ptr<T[]>
operator[]
[]를 하면 awkward 동작을 수행할 수 있음
shared_ptr은,
derived-to-base pointer conversion을 지원
이건 single object에서만 가능
array에서는 되지 않음
그래서
unique_ptr<T[]>은 이런 conversion을 막음
built-in array에 대한 alternative를 사용
std::array
std::vector
std::string
dumb array에 smart pointer의 선언은 늘 항상 나쁜 설계의 신호임
Things to remember
1) 자원의 수명관리를 편하게 함
2) shared_ptr size는 unique_ptr의 2배 size
3) custom deleter 지원
4) raw pointer에서 shared_ptr을 생성하는 것은 피해야 함
'programming > programming general' 카테고리의 다른 글
[C++] EMCP Lambda expression (0) | 2018.03.25 |
---|---|
[C++] EMCP Item 20: use std::weak_ptr for std::shared_ptr-like pointers that can dangle. (0) | 2018.03.25 |
[C++] Smart Pointers (0) | 2018.03.25 |
[C++] Atomic class 구현 (0) | 2015.07.04 |
댓글