-
항목19. 소유권 공유 자원의 관리에는 std__shared_ptr를 사용하라Effective Modern C++ 2022. 8. 5. 00:29
std::shared_ptr 은 공유된 소유권을 통해 관리한다. 객체를 가르키던 마지막 std::shared_ptr이 더 이상 가리키지 않게되면 객체는 자동으로 파괴된다. 이는 참조 횟수를 통해 이루어진다. 생성자에서 참조 횟수를 증가시키고 소멸자에서 감소시킨다. 기존의 std::shared_ptr을 이용해 새 std::shared_pt에 이동 생성, 이동 배정시 참조 횟수 증가 없음.
참조 횟수 관리는 다음 과 같은 특징이 있다.
- std::shared_ptr의 크기는 생 포인터의 두 배이다.
- 참조 횟수를 담을 메모리를 반드시 동적으로 할당해야 한다.
- 참조 횟수의 증가와 감소는 원자적 연산이어야 한다. (다중 스레드때문에)
커스텀 삭제자의 형식이 std::unique_ptr과 다름
// std::unique_ptr은 삭제자가 형식의 일부임! std::unique<Widget, decltype(loggingDel)> upw(new Widget, loggingDel); // std::shared_ptr은 삭제자가 형식의 일부가 아님! std::shared_ptr<Widget> spw(new Widget, loggingDel);즉 다른 형식의 삭제자를 가지는 두 std::shared_ptr끼리는 같은 컨테이너에 담을 수 있고, 하나를 다른 하나에 배정할 수도 있다. 또한 커스텀 삭제자를 지정해도 객체의 크기가 변하지 않는다.
- std::make_shared는 항상 제어 블록을 생성한다. 이 함수는 공유 포인터가 가리킬 객체를 새로 생성하므로, std::make_shared가 호출되는 시점에서 그 객체에 대한 제어 블록이 존재할 가능성은 전혀 없다.
- 고유 소유권 포인터(std::unique_ptr나 std::auto_ptr)로부터 std::shared_ptr 객체를 생성하면 제어 블록이 생성된다.
- 생 포인터로 std::shared_ptr 생성자를 호출하면 제어 블록이 생성된다.
⇒ 하나의 생 포인터로 여러 개의 std::shared_ptr을 생성하지 말자! 앵간하면 사용하지말고 꼭 써야할 때는 생 포인터 변수를 거치지 말고 new의 결과를 직접 전달하자!
std::shared_ptr<Widget> spw1(new Widget, loggingDel);⇒ 커스텀 삭제자를 지정하지 않을 경우 std::make_shared를 사용하자!
void Widget::process(){ ... processedWidgets.emplace_back(this); // emplace_back => 인수를 이용해 직접 객체를생성해 넣어줌 }위 코드에서 this(생 포인터)를 std::shared_ptr들의 컨테이너에 넘겨주면 새 제어 블럭이 생긴다.
이를 방지하기 위해 사용하는 것이 std::enable_shared_from_this이다. 이것을 상속하면class Widget: public std::enable_shared_from_this<Widget> { public: ... void process(); ... }이 템플릿의 형식 인수로는 파생할 클래스의 이름을 지정해야함. 이러고 나면
void Widget::process(){ ... processedWidgets.emplace_back(shared_from_this()); // emplace_back => 인수를 이용해 직접 객체를생성해 넣어줌 }이는 현재 객체에 이미 제어 블록이 연관되어 있다고 가정한다. 그런 std::shared_ptr이 존재하지 않으면 함수의 행동은 정의되지 않는다.
std::shared_ptr이 유효한 객체를 가리키기도 전에 클라이언트가 shared_from_this를 호출하는 것을 방지하기 위해, std::enable_shared_from_this을 상속받은 클래스는 자신의 생성자들을 private로 선언한다. 그리고 클라이언트가 객체를 생성할 수 있도록, std::shared_ptr을 돌려주는 팩터리 함수를 제공한다.class Widget : public std::enable_shared_from_this<Widget> { public: // 팩터리 함수; 인수들을 전용 생성자에 완벽하게 전달한다. template <typename... Ts> static std::shared_ptr<Widget> create(Ts&&... params); ... private: ... // 생성자들 }크지 않은 비용을 치르는 대신 std::shared_ptr을 사용하면 동적 할당 자원의 수명이 자동으로 관리된다는 이득이 생긴다. (소유권 공유 객체)
독점 소유권으로도 충분하다면, 심지어 반드시 충분하지는 않아도 충분할 가능성이 있다면, std::unique_ptr을 사용하자. std::unique_ptr은 생 포인터와 성능은 거의 같고, std::unique_ptr에서 std::shared_ptr로의 업그레이드는 쉽다. 하지만 그 역은 안된다.'Effective Modern C++' 카테고리의 다른 글
항목21. new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라 (0) 2022.08.11 항목20. std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하면 std::weak_ptr을 사용하라 (0) 2022.08.11 항목18. 소유권 독점 자원 관리는 std__unique_ptr를 사용하라 (0) 2022.08.03 항목17. 특수 멤버 함수들의 자동 작성 조건을 숙지하라. (0) 2022.07.29 항목16. const멤버 함수를 스레드에 안전하게 작성하라 (0) 2022.07.29