C++

std::unique_ptr & std::shared_ptr & std::weak_ptr

Keisa 2023. 9. 28. 19:04

● std::unique_ptr

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

2. 기본적으로 자원 파괴는 delete를 통해 일어나나, 커스텀 삭제자를 지정할 수 있다. 상태 있는 삭제자나 함수 포인터를 사용하면 std::unique_ptr 객체의 크기가 커진다

: 함수포인터를 사용하면 해당 포인터의 크기만큼 크기가 증가

: 갈무리 없는 람다표현식에서는 크기 증가가 없다

3. std::unique_ptr를 std::shared_ptr로 손쉽게 변환할 수 있다

 

● std::shared_ptr

1. std::shared_ptr은 임의의 공유자원의 수명을 편리하게 관리할 수 있는 수단을 제공

2. 대체로 std::shared_ptr 객체는 그 크기가 std::unique_ptr객체의 두배이먀, 제어블록에 관련된 추가 부담을 유발하며 원자적 참조횟수 조작을 요구한다

3. 자원은 기본적으로 delete를 통해 파괴되나, 커스텀 삭제자도 지원한다. 삭제자의 형식은 std::shared_ptr의 형식에 아무런 영향을 미치지 않는다. 

: delete[]를 지원하지 않음

4. 생포인터 형식으로부터의 생성은 피해야한다

 

● std::weak_ptr

: 본질적으로 std::shared_ptr과 동일하다. 객체의 크기는 std::shared_ptr과 같으며 같은 제어블록을 이용하여, 생성·파괴·배정같은 연산의 원자적 참조 횟수 조작에 관여한다.

std::weak_ptr은 제어블록의 두번째 참조횟수를 이용하며 객체의 소유권 공유에 참여하지 않으며, 피지침 객체의 참조횟수에 영향을 미치지 않는다.

std::weak_ptr자체로는 원래 객체를 참조할 수 없고, 반드시 std::shared_ptr로 변환해서 사용해야한다. 이때 가리키고 있는 객체가 이미 소멸했다면 빈 std::shared_ptr로 변환되고, 아닐경우 해당 객체를 가리키는 shared_ptr로 변환된다.

제어블록은 객체가 해제되더라도 std::weak_ptr의 참조까지 다사라져야 해제된다. 아무것도 가리키지 않는 std::shared_ptr은 false로 형변환된다.

 

 weak_ptr은 할당 받은 자원을 직접적으로 사용하지 못한다.

 직접적인 사용을 막기 위해서 operator-> 및 operator*와 get() 함수가 구현되어 있지 않다.

 lock함수를 통해서 우회적으로 사용을 지원한다.

_NODISCARD shared_ptr<_Ty> lock() const noexcept { // convert to shared_ptr
    // shared_ptr 객체를 생성하고
    shared_ptr<_Ty> _Ret;
    // weak_ptr의 객체를 통해서 shared_ptr을 생성합니다. 
    (void) _Ret._Construct_from_weak(*this);
    return _Ret;
}
template <class _Ty2>
bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other) noexcept {
    // implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
    // weak_ptr의 control block이 살아 있는지 확인하고
    // 참조카운트를 증가시킵니다. (_Incref_nz 함수)
    if (_Other._Rep && _Other._Rep->_Incref_nz()) {
        _Ptr = _Other._Ptr;
        _Rep = _Other._Rep;
        return true;
    }

    return false;
}

lock함수를 통해 얻은 std::shared_ptr은 std::weak_ptr의 참조카운트가 아닌 std::shared_ptr의 참조카운트를 증가시켜 lock을 통해 반환받은 객체를 사용하는 도중 객체가 사라지는 일이 없도록 한다.

std::shared_ptr만으로는 완전한 스마트포인터의 기능을 제공할 수 없기때문에 std:weak_ptr을 사용한다. 대표적으로 순환참조 문제가 있다.

 

class Person
{
    //... 생략
    std::weak_ptr<Person> m_partner; 
    //... 생략
public:
    //... 생략
}
{
    // woong과 soo라는 Person의 shared_ptr 객체를 생성합니다.
    auto pWoong = std::make_shared<Person>("woong");
    auto pSoo = std::make_shared<Person>("soo");

    // woong과 soo과 서로 파트너로 설정합니다. 
    // 내부적으로 서로가 서로를 참조하는 형태로 구현됩니다.
    pWoong->SetPartner(pSoo);
    pSoo->SetPartner(pWoong);
}

std::weak_ptr을 사용하는 경우에는 해당 문제가 없어진다.

 

참조: https://jungwoong.tistory.com/50

 

[c++] weak_ptr

이번장에서는 weak_ptr에 대해서 알아 보도록 합니다. shared_ptr를 구현하면서 참조 카운트에 영향을 받지 않는 스마트 포인터가 필요했는데 weak_ptr을 사용하면 shared_ptr가 관리하는 자원(메모리)을

jungwoong.tistory.com

 

언리얼 스마트 포인터

https://devjino.tistory.com/254

 

[UE4] 스마트포인터 - TSharedPtr, TWeakPtr, TUniquePtr

UE4는 C++11 스마트 포인터의 커스텀 구현을 제공하고 있으며 다음과 같은 이점을 제공합니다. 메모리 누수 방지 스마트 포인터(TWeakPtr, TWeakObjectPtr 제외)는 공유 참조가 존재하지 않을 때 객체를

devjino.tistory.com