본문 바로가기

BackEnd/C랑 C++

C++ 스마트 포인터를 알아보자 - (2) shared_ptr

오늘은 스마트 포인터 중...

두 번째인 shared_ptr에 대해서 알아볼거다

 

요즘 네이버 블챌 이벤트를 하고 있는데

가운데 정렬로 하니까 사진 첨부하기가 너무 좋았어서

왼쪽정렬파였는데 바꿔 보려고 한다 ^_^

 

 

++ 이전 글 올리고 다음 날 올릴려고 했는데

다른걸 먼저 올렸더니 잊혀질 뻔 했다

 


1. 스마트 포인터에는 뭐가 있지?

스마트 포인터는 3가지가 있다

 

1) unique_ptr

2) shared_ptr (오늘할거)

3) weak_ptr

 

 

1-1) unique_ptr이 뭐였더라 (링크 첨부 ^^)

 

첫 번째, unique_ptr은 포인터를 통해 Unique한 소유권을 가지고

unique_ptr 범위를 벗어날 때 해당 개체를 처리하는 스마트 포인터이다

 

두 번째, make_unique() 또는 new를 사용해서 생성할 수 있다

 

세 번째,  고유한 소유권이 있기 때문에, 복사 대신 move를 사용하여 구현한다

 


 

2. shared_ptr

unique_ptr은 고유한.... 유니크한 느낌이 드는 이름이었는데

shared는 뭔가 공유하는 공유될 것 같은 느낌이 든다

 

 

바로 사용 예시로 넘어가도록 하겠다

 

#include <memory>
#include <iostream>

using namespace std;

int main ( )
{
    std::shared_ptr<int> ptr = std::make_shared<int>(10);
    cout << ptr << endl;
    cout << *ptr << endl;

    return 0;
}

 

와~ ptr 출력하면 10이 나오겠지? ㅋ 하면 안된다

예시에서 사용한 것 처럼, ptr 자체를 출력하면 포인터기 때문에

포인터의 주소값이 나오고,

* 연산자를 사용하면 포인터가 가르키는 값인 10이 나온다

또한 shared라는 이름에서 느낌이 오듯이

unique_ptr과 다르게 shared_ptr은 복사(copy)가 되고

같은 값을 가르키는 것도 가능하다

 

int main ( )
{
    std::shared_ptr<int> ptr = std::make_shared<int>(10);
    cout << *ptr << endl;

    std::shared_ptr<int> ptr_cp = ptr;
    cout << *ptr_cp << endl;

    return 0;
}

 

 

ptr_cp라는 shared_ptr에 ptr의 값을 할당해도

원하는 결과가 나온 모습이다

요걸 그림으로 표현하면 아래와 같다

 

 

갓MS

 

Diagram1을 보면, Control Block이란걸 p1이 참조하고 있다

여기서 p1은 shared_ptr 타입의 변수를 뜻하고

포인터니까 주소 값을 가진다(참조한다)고 이해하면 좋을 것 같다

 

 

2-1) Reference Count (참조 횟수)

 

shared_ptr에는 참조횟수라는 개념이 있다

unique_ptr과는 다르게 shared_ptr는 자기 포인터를 갖고 있는 동안

소유권을 공유할 수 있게 해준다

(포인터의 주소를 가져오고 싶을 때는 get()을 사용할 수 있다)

소유권이 늘어날 때(사용 횟수가 늘어날 때) 참조 횟수는 증가한다

 

또한 shared_ptr 객체를 소유하는 것이 아무도 없으면 참조 횟수도 0이 될 수 있다

참조 횟수는 use_count() 로 가져오면 된다

 

그림으로 봐보장

 

 

 

Diagram2를 보면, Diagram 1처럼 Control Block을 p1과 p2가 참조하고 있다

다른 점은, p1도 참조하고 → 참조 카운트 +1

p2도 참조하기 때문에 → 참조 카운트 +1

그림 상단의 Ref count가 2가 된 것을 볼 수 있다

 

참조하고 있는 p1과 p2가 모두 사라지면, Ref count도 0이 되는데

그러면 자동으로 삭제가 되어 메모리를 따로 해제하지 않아도 된다

 

말이 어렵지만 코드로 출력 몇 번  해보면 알 수 있다

 

int main ( )
{
    std::shared_ptr<int> ptr = std::make_shared<int>(10);
    cout << *ptr << endl;

    std::shared_ptr<int> ptr_cp = ptr;
    cout << *ptr_cp << endl;

    std::shared_ptr<int> ptr_cp2 = ptr_cp;

    cout << "Ref Count: " << ptr.use_count() << endl;

    return 0;
}

 

결과 짠

 

ptr의 use_count가 3으로 출력이 됐다

 

1) ptr에 대해 make_shared로 만들어서 카운트 증가

2) ptr 포인터를 ptr_cp에서도 갖고 있음

3) ptr의 포인터를 갖는 ptr_cp의 포인터를 ptr_cp2에서도 갖고 있음

(ptr의 포인터를 가짐)

 

예시를 하나 더 들어보겠다

ptr을 사용안하면 참조 카운트도 자동으로 줄어드는지

확인하기 위해서, 지역 함수에서

shared_ptr 포인터를 복사하도록 해보겠다

 

void func(shared_ptr<int> ptr)
{
    std::shared_ptr<int> ptr_cp = ptr;
    cout << *ptr_cp << endl;

    std::shared_ptr<int> ptr_cp2 = ptr_cp;
    cout << __func__ << "::Ref Count: " << ptr.use_count() << endl;
}

int main ( )
{
    std::shared_ptr<int> ptr = std::make_shared<int>(10);
    cout << *ptr << endl;

    func(ptr);
    cout << __func__ << "::Ref Count: " << ptr.use_count() << endl;

    return 0;
}

 

 

main에서는 shared_ptr 객체인 ptr만 만들어줬고

func() 함수에서 값 복사가 수행된다

그리고 파라미터로 포인터를 넘겨주면서 Ref count도 하나 더 증가했다

 

함수가 종료된 후에는 func()에서 수행한 참조는 없어졌기 때문에

main에서는 다시 Ref Count가 1로 되었다

 

 

 

3. shared_ptr를 value로 갖는 자료구조

 

shared_ptr은 map이나 vector 같은 자료구조에서

value 값으로도 많이 쓰인다

 

map을 감싸는 모양으로도 쓰이긴 하지만

포인터로 관리할 필요가 없다면 굳이?

 

바로 예시를 보면서 설명하도록 하겠다

 

#include <map>

int main ( )
{
    map<string, shared_ptr<int>> mapTest;

    auto ptr = make_shared<int>(10);
    cout << __func__ << "::Ref Count: " << ptr.use_count() << endl;

    mapTest["first"] = ptr;

    cout << __func__ << "::Ref Count: " << ptr.use_count() << endl;

    for( auto const &val : mapTest )
    {
        cout << val.first << ": " << *val.second << endl;
    }

    return 0;
}

 

 

이렇게 map의 value 값으로도 사용할 수 있다

map이 소멸된다면 value로 있는 shared_ptr의 소멸자도 자동으로 불린다

따로 관리 안해줘도 됨

 

편하게 유용하게 사용할 수 있지만

포인터는 포인터기 때문에 항상 조심하도록 하자

 


참고자료

 

https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count

 

std::shared_ptr::use_count - cppreference.com

long use_count() const noexcept; Returns the number of different shared_ptr instances (this included) managing the current object. If there is no managed object, ​0​ is returned. In multithreaded environment, the value returned by use_count is approxim

en.cppreference.com