배움 저장소

[C++] unique_ptr, shared_ptr, weak_ptr 본문

Programming Language/C++

[C++] unique_ptr, shared_ptr, weak_ptr

시옷지읏 2021. 11. 7. 19:08

1. unique_ptr

여러 포인터가 하나의 대상을 가르키는 걸 방지해준다. 해당 값을 가리키는 ptr를 한 개로 제한하고 싶을때 사용하면 된다. 만약 여러 unique_ptr이 하나의 대상을 가르키면 컴파일 에러가 난다. 특정 데이터의 소유권을 다른 unique_ptr로 옮기려면 move()함수를 이용하자

 unique_ptr<A> ptr1 (new A);

 // Error: can't copy unique_ptr
 unique_ptr<A> ptr2 = ptr1;
 
 // Works, resource now stored in ptr2
unique_ptr<A> ptr2 = move(ptr1);

 

예제

// C++ program to illustrate the use of unique_ptr
#include <iostream>
#include <memory>
using namespace std;
  
class A {
public:
    void show(){ cout << "A::show()" << endl; }
};
  
int main()
{
    unique_ptr<A> p1(new A);
    p1->show();						// -> "A::show()"
    cout << p1.get() << endl; 		// -> 0x1c4ac20

    // transfers ownership to p2
    unique_ptr<A> p2 = move(p1);
    p2->show();						// ->"A::show()" 
    cout << p1.get() << endl; 		// -> 0
    cout << p2.get() << endl; 		// -> 0x1c4ac20
  
    // transfers ownership to p3
    unique_ptr<A> p3 = move(p2);
    p3->show(); 					// -> "A::show()"
    cout << p1.get() << endl; 		// -> 0
    cout << p2.get() << endl; 		// -> 0
    cout << p3.get() << endl; 		// -> 0x1c4ac20
  
    return 0;
}

 

만약 unique_ptr을 반환하는 함수가 있다면 사용법은 다음과 같다.

unique_ptr<A> fun()
{
    unique_ptr<A> ptr(new A);
    /* ...
       ... */
    return ptr;
}

만약 함수가 호출되면서 해당 unique_ptr이 저장되지 않는다면 unique_ptr 정보는 지워질 것이다. delete를 따로 사용해주어야하는 포인터와 달리 Memory 관리를 하기가 편하다.

2. shared_ptr

unique_ptr과 달리 여러 shared_ptr이 동일한 대상을 가리킬 수 있다. 그리고 해당 대상을 가리키고 있는 shared_ptr이 몇 개인지 get()함수를 사용하여 확인할 수 있다. 동일한 대상을 가르키는 여러 shared_ptr를 사용하고 싶을 때 사용하자. Constructor가 실행될 때 count가 늘어나고 Destructor가 실행될 때 count가 줄어든다. count=0이되면 메모리 해제된다.

#include <iostream>
#include <memory>
using namespace std;

class A {
public:
    void show()
    {
        cout << "A::show()" << endl;
    }
};

int main()
{
    A* test = new A();				// 해당 ptr는 counting 고려대상이 아니다
    shared_ptr<A> p1(test);
    // shared_ptr<A> p1(new A);
    cout << p1.get() << endl;		// -> 02800510
    p1->show();						// -> "A::show()"
    shared_ptr<A> p2(p1);
    p2->show();						// -> "A::show()"
    cout << p1.get() << endl;		// -> 02800510
    cout << p2.get() << endl;		// -> 02800510
  
    // Returns the number of shared_ptr objects
    // referring to the same managed object.
    cout << p1.use_count() << endl; // -> 2
    cout << p2.use_count() << endl; // -> 2
    
    // Relinquishes ownership of p1 on the object
    // and pointer becomes NULL
    p1.reset();
    cout << p1.get() << endl;		// -> 00000000
    cout << p2.use_count() << endl; // -> 1
    cout << p2.get() << endl;		// -> 02800510
}

 

3. weak_ptr

 shared_ptr과 동일한 기능을 제공한다. 한 대상을 여러 weak_ptr이 가르킬 수 있다. shared_ptr과 다른점은 Destructor를 호출할 때 사용되는 counting에 아무 영향을 주지 않는다. 이 덕분에 순환참조 문제를 해결할 수 있다. 

 

순환참조의 예제는 수까락님 블로그에 잘 소개되어 있다. http://sweeper.egloos.com/2826435

해당 글의 9) Circular references이다.

#include <memory>    // for shared_ptr
#include <vector>
 
using namespace std;
 
class User;
typedef shared_ptr<User> UserPtr;
 
class Party
{
public:
    Party() {}
    ~Party() { m_MemberList.clear(); }
 
public:
    void AddMember(const UserPtr& member)
    {
        m_MemberList.push_back(member);
    }
 
private:
    typedef vector<UserPtr> MemberList;
    MemberList m_MemberList;
};
typedef shared_ptr<Party> PartyPtr;
 
class User
{
public:
    void SetParty(const PartyPtr& party)
    {
        m_Party = party;
    }
 
private:
    PartyPtr m_Party;
};
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    PartyPtr party(new Party);
 
    for (int i = 0; i < 5; i++)
    {
        // 이 user는 이 스코프 안에서 소멸되지만,
        // 아래 party->AddMember로 인해 이 스코프가 종료되어도 user의 refCount = 1
        UserPtr user(new User);
 
        // 아래 과정에서 순환 참조가 발생한다.
        party->AddMember(user);
        user->SetParty(party);
    }
 
    // 여기에서 party.reset을 수행해 보지만,
    // 5명의 파티원이 party 객체를 물고 있어 아직 refCount = 5 의 상태
    // 따라서, party가 소멸되지 못하고, party의 vector에 저장된 user 객체들 역시 소멸되지 못한다.
    party.reset();
 
    return 0;
}

 

 위 상황에서 파티 class 내부에 shared_ptr 대신 weak_ptr을 사용하면 해결!

 

주의

weak_ptr은 shared_ptr이나 unique_ptr처럼 (new 연산자)를 활용하여 자신을 초기화할 수 없다. weak_ptr은 shared_ptr 혹은 같은 weak_ptr을 사용하여 초기화해야 한다.

 다음 예제는 수까락님 블로그에서 가져왔다

#include <memory>    //for shared_ptr/weak_ptr
#include <iostream>
 
using namespace std;
 
int main(int argc, char** argv)
{
    // strong refCount = 1
    shared_ptr<int> sp1(new int(5));
 
    // shared_ptr sp1으로부터 복사 생성
    // weak_ptr이 참조하여, strong refCount = 1, weak refCount = 1
    weak_ptr<int> wp1 = sp1;
    {
        // wp1이 참조하고 있던 sp1을 weak_ptr::lock 메써드를 이용해 sp2가 참조
        // string refCount = 2, weak refCount = 1
        shared_ptr<int> sp2 = wp1.lock();
        if (sp2)
        {
            // weak_ptr<_Ty>의 _Ty 포인터에 엑세스 하려면
            // 이렇게 shared_ptr로 convert하는 방법 밖에 없다
        }
        // sp2가 여기에서 소멸, strong RefCount = 1, weak refCount = 1
    }
 
    // sp1.reset으로 인해 strong refCount = 0, 즉 sp1 소멸
    // wp1이 참조하고 있던 sp1이 소멸되었으므로, wp1은 expired
    sp1.reset();
 
    // expired된 wp1은 참조하고 있는 shared_ptr이 없다.
    // 따라서, sp3도 empty
    shared_ptr<int> sp3 = wp1.lock();
    if (sp3)
    {
        // 여기 문장은 실행되지 않는다
    }
 
    return 0;
}

 

 

 

 

참고

https://www.geeksforgeeks.org/auto_ptr-unique_ptr-shared_ptr-weak_ptr-2/

http://sweeper.egloos.com/2826435

 

 

'Programming Language > C++' 카테고리의 다른 글

[홍정모의 따라하며 배우는 C++] 0. 시작해봅시다.  (0) 2021.12.10
[C++] Template  (0) 2021.11.13
[C/C++] Left Shift and Right Shift  (0) 2021.11.11
부호가 있는 이진수 1000은 0일까?  (0) 2021.10.19
[C++] Iterator  (0) 2021.10.09
Comments