티스토리 뷰

shared_ptr의 특징

 C C++ 를 프로그래밍을 하다보면 가장 어려운 점이 바로 메모리 관리이다. 이전 항목에서 배운 unique_ptr 를 사용하면 자동으로 메모리를 해주기는 하지만 여러 객체에서 소유하는 경우에는 unique_ptr 를 사용할 수 없다. 이때 사용할 수 있는 스마트 포인터가 shared_ptr 이다. shared_ptr 는 객체의 소멸시점을 관리하기 위하여 reference count 를 사용한다. 이 reference count0 이 되면 메모리를 해제하게 된다. 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++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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
글 보관함