티스토리 뷰
[Effective Modern C++] 항목 19. 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라
pppgod 2019. 11. 10. 22:37shared_ptr의 특징
C 와 C++ 를 프로그래밍을 하다보면 가장 어려운 점이 바로 메모리 관리이다. 이전 항목에서 배운 unique_ptr 를 사용하면 자동으로 메모리를 해주기는 하지만 여러 객체에서 소유하는 경우에는 unique_ptr 를 사용할 수 없다. 이때 사용할 수 있는 스마트 포인터가 shared_ptr 이다. shared_ptr 는 객체의 소멸시점을 관리하기 위하여 reference count 를 사용한다. 이 reference count 가 0 이 되면 메모리를 해제하게 된다. shared_ptr 는 내부적으로 다음과 같이 구현되어 있다.
-
shared_ptr의 크기는 생 포인터의 두 배이다.
생 포인터의 크기와 reference count 를 가르키는 포인터를 갖고 있어야 하기 때문에 생 포인터의 두배의 크기를 갖고 있다.
-
참조 횟수를 담을 메모리를 반드시 동적으로 할당해야 한다.
reference count 를 가르키는 객체는 공유되는 자원이기 때문에 동적으로 할당해야 한다.
-
참조 횟수의 증가와 감소가 반드시 원자적 연산이어야 한다.
멀티스레드 프로그램에서 shared_ptr 를 사용할 수 있기 때문에 atomic 하게 증가 감소를 해야한다. atomic 연산으로 인해 성능 감소가 있다. 이 연산을 줄이면 성능을 높일 수 있는게 그 중 한가지 방법이 바로 이동 연산을 사용하는 것이다.
커스텀 삭제자
unque_ptr 와 같이 shared_ptr 에서도 커스텀 삭제자를 구현 할 수 있다.
auto loggingDel = [](Widget *pw)
{
makeLogEntry(pw);
delete pw;
};
std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel);
std::shared_ptr<Widget> spw(new Widget, loggingDel);
shared_ptr 는 unique_ptr 와 다르게 커스텀 삭제자의 타입을 지정해주지 않아도 되는 유연함을 갖고 있다. 덕분에 다음과 같은 작성도 가능하다.
auto customDeleter1 = [](Widget *pw) { ... };
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 };
뿐만 아니라 커스텀 삭제자가 아무리 커져도 shared_ptr 의 크기는 변하지 않는다. 그 이유는 앞에서 말했던 두 개의 포인터 중 로우 포인터를 제외한 다른 하나의 포인터가 관리를 하기 때문이다. 이 포인터가 가르키는 객체는 바로 제어 블록(control block)이다.
Control Block
control block 은 shared_ptr 의 소멸에 대해 관리하는 객체이다. 앞서 말한 reference_count 와 커스텀 삭제자를 갖고 관리하는 객체가 바로 control block 이다. control block 객체가 이미 생성되어 있다면 다시 생성해서는 안된다. control block 의 존재 여부를 알 수는 없지만 몇 가지 규칙들을 유추할 수는 있다.
-
std::make__shared 는 항상 제어 블록을 생성한다.
이 함수는 항상 새로운 shared_ptr 객체를 생성하기 때문에 제어 블록이 존재할 가능성이 없다.
-
고유 소유권 포인터로부터 std::shared_ptr 객체를 생성하면 제어 블록이 생성된다.
unique_ptr 는 제어 블록을 사용하지 않기 때문에 존재할 가능성이 없다.
-
생 포인터로 std::shared_ptr 생성자를 호출하면 제어 블록이 생성된다.
생 포인터의 경우에는 제어 블록이 없을것이라 가정하여 제어 블록을 생성한다. 만약 이미 제어 블록이 있다면 생성자로 shared_ptr 나 weak_ptr 를 사용하면 제어 블록을 만들지 않는다.
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
- Total
- Today
- Yesterday
- C
- Join
- forward
- 포인터
- Overloading
- Forwarding
- Override
- 보편 참조
- Effective Modern C++
- 다이소
- Perfect
- Unreal
- std::forward
- Effective
- 보편참조
- async
- MOVE
- C++11
- 람다
- CPP
- Future
- Modern
- C++14
- const
- 발아시기
- std::move
- C++
- thread
- auto
- detach
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |