Corgi Dog Bark

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스마트 포인터 : Shared Ptr
    카테고리 없음 2022. 5. 11. 00:16
    반응형

    지난번에 봤던 Unique Ptr의 경우(https://guru.tistory.com/108), 생성한 객체는 오직 ! 그 객체를 가리키는 Unique 한 Pointer 로만 관리한다로 정리할 수 있겠습니다. 하지만, Smart Pointer 에는 객체들을 유일한 소유권만이 아닌, 다 같이 공유하도록 하면서 리소스를 관리해줄 수 있는데, 이를 Shared Ptr이라 합니다. 왜 Shared ptr 이 나왔는지는 밑에서 이어 설명하겠습니다.

     

    Shared Ptr 왜 쓰는데? 

    A shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used to point to member objects while owning the object they belong to. [ by Cpp Reference ]

    Shared Ptr 이 도대체 왜 쓰이는지 궁금할 때 Google 에 검색해보면, StackOverFlow 에는 "좋아요" 를 무려 2050 개가 달린 답변글이 하나 있습니다. 여기서 질문은 정말 간단한데, "왜 Shared Ptr 을 쓰냐는 것"입니다. 그럼 지금부터 이에 달린 답변을 간단하게 살펴보겠습니다. 

     

    뭐 Boost 의 라이브러리의 스마트 포인터에서부터 시작했다 ~~ 고 답변은 시작합니다. 그리고 스마트 포인터의 필요성 , 즉 스마트 포인터가 없으면, 프로그래머가 직접적으로 객체를 생성 및 관리해줘야 하는데, 스마트 포인터로 인하여 그 수고로움을 덜 수 있다고 이야기하고, 그리고 드디어 Shared 포인터에 관한 이야기가 나옵니다.

     

    스마트 포인터 그중에서도 Shared Ptr는 객체의 라이프타임이 더 복잡할때, 그리고 다른 객체와 엮여 있을 때, 아주 유용하게 쓰인다 할 수 있다고 답변해주고 있습니다. 이에 관한 간단한 예시를 들어보겠습니다.

    void f()
    {
        typedef std::shared_ptr<MyObject> MyObjectPtr; // MyObject 를 관리하기 위한 Shared Ptr! Alias
        MyObjectPtr p1; // Empty
        {
            MyObjectPtr p2(new MyObject()); // MyObject 에 대한 reference count 가 1 증가한다.
            p1 = p2; // p1 또한 p2 객체를 가르키고 있어 Ref count = 2 가 됩니다.
        } // p2 가 스코프를 빠져나가면서, 없어진다. 그리고 이는 p1 에도 알려지게 된다.
    } // p1 이 소멸되면서, 더 이상 MyObject 를 가르키는 RefCount = 0 이 된다.따라서 객체는 소멸합니다.

    주석을 옆에 달아 놨는데, Shared Ptr 은 현재 shared ptr 이 가르키고 있는 객체를 누가누가 참조하고 있는지를 나타낼 때 쓰는 녀석이라 생각하면 됩니다. 위와 같은 상황을 Unique Ptr 로 놔버리면, 내부에 있는 Scope를 나가자마자 바로 객체는 소멸될 것입니다. 하지만 Shared Ptr로 설정을 해놨고, 이 포인터는 우리의 객체(여기서는 <MyObject>)를 참조하고 있는 Count를 내부적으로 가지고 있기 때문에, scope를 벗어놔도 소멸 되지는 않습니다. (개인적으로는 Unique Ptr 의 상위 호환 버전이라 생각합니다.)

     

     

    Shared Ptr 작동 원리

    그럼 어떻게 Shared Ptr이 이렇게 스마트 하게 작동하는 지를 살펴보자면,

    Shared Ptr의 간략한 그림.

    Shared Ptr 은 내부적으로 Control Block 을 지니고 있어, <T Object> 를 몇명이나 가르키는지 내부적으로 나타내고 있습니다. 또한 이를 통해, 내부의 Ref Count 를 관리해줍니다. 그럼 다음으로 Shared Ptr을 작동시켜보는 간단한 예제를 만들어 보겠습니다.

     

     

     

    Shared Ptr 처럼 동작하는 포인터 만들어보자.

    전편에서도 이야기 했지만, 이는 단순히 Shared Ptr이 어떤식으로 작동하는지를 파악하기 위함이지, 내부적으로는 다르게 구현되어있지 않습니다. 하지만 작동원리는 비슷하니 대략 "아 이런느낌이구나" 하고 감 잡고 넘어가면 좋을 것 같아 첨부하였습니다.

    template<typename T>
    class Shared_Pointer {
    	T* data;
    	int* count;
    
    public:
    	// 생성자 count 와 data 를 가르켜주는 T* 생성.
    	explicit Shared_Pointer(T* data) : data(data), count(new int(1)) { }
        
        // Destructor : Ref Count 가 0 이라면 Data를 제거해준다.
    	~Shared_Pointer() {
    		--(*count);
    		if (*count == 0) {
    			delete data;
    		}
    	}
    
    	// Copy Constructor
    	Shared_Pointer(const Shared_Pointer& copy) : data(copy.data), count(copy.count){
        	// copy 를 참조하는 친구가 늘어났으므로, ++(*count) 해준다.
            // 여기서는 생성자이므로, Assignment Operator 처럼, 업데이트를 해주지 않는다.
    		++(*count);
    	}
    
    	// Assignment Operator
    	Shared_Pointer& operator=(const Shared_Pointer& rhs) {
    		T* old_data = data;
    		int* old_count = count;
    
    		data = rhs.data;
    		count = rhs.count;
    
    		// 바뀐 것에 대한 업데이트
    		++(*count); --(*old_count);
    
    		// old count 에 대한 소멸권.
    		if (*old_count == 0) {
    			delete old_data;
    		}
    	}
    
    	T* operator->() const {return data;}
    	T& operator*() const {return *data;}
    
    	// Smart Pointer 에 대한 접근
    	T* get() const {return data;}
    	explicit operator bool() const {return data;} // if 문 사용시.
    };

     

     

    Shared Ptr 특징 정리

    1. Shared Ptr 은 make_shared 로 만들어라.

    shared Ptr 은 내부적으로 Counting block 을 가지고 있기 때문에, 그냥 shared Ptr 만 호출하면 동적 할당을 두번해주게 됩니다. 따라서 그냥 Shared Ptr 사용시 Data 에 한번 , Data 를 counting 해주기 위한 Counting Block 에 한번 하여 총 2번 호출 되므로, 그냥 make_shared 를 사용하여 , 한번에 동적할당 해주도록 합시다.

     

    2. Shared Ptr 생성시 주소 전달을 주의하자.

    Shared Ptr 생성시, 주소를 따로 전달해주게 된다면, 다른 카운팅으로 객체를 사용하게 됩니다. 즉 ,
    A * a = new A();
    std::shared_ptr<A> p1(a);
    std::shared_ptr<A> p2(a); 
    사용시, p1 과 p2 는 자기만의 Counting Block 을 가지고 있어, p1 와 p2 는 따로 놀게 됩니다.
    반응형

    댓글

Designed by Tistory.