C , C++, C#

[C/C++] Shared_ptr

vhxpffltm 2020. 1. 6. 23:05

unique_ptr은 동적으로 할당된 객체를 관리하는 데 유용한 클래스이다. 하지만 소유권을 단 하나만 가질 수 있는 단점이 있다.

 

여러 객체가 하나의 동적 할당 객체를 소유한다면 이것을 삭제할 때 그 주체가 누구인지 불분명하다.

 

shared_ptr 포인터 타입은 이런 경우를 위한 것이다. 특정 자원을 몇 개의 객체에서 가리키는지를 추적한 다음 그 수가 0 이 되야만 비로소 해제를 시켜주는 방식의 포인터이다.

 

내부 참조 카운트를 이용한 방법으로 적재된 객체에 대한 포인터를 유지하고 있는 객체의 수를 확인하고 범위를 벗어나는 마지막 shared 포인터만 적재 객체에 삭제를 호출한다.

 

전체코드는 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<iostream>
#include<vector>
#include<memory>
 
class A {
    int *ptr;
public:
    A() {
        ptr = new int[100];
        std::cout << "A 자원을 획득" << std::endl;
    }
    ~A() {
        std::cout << "A 소멸자, 자원해제" << std::endl;
        delete[] ptr;
    }
};
 
int main() {
 
    std::vector<std::shared_ptr<A>> arr;
    arr.push_back(std::shared_ptr<A>(new A()));
    arr.push_back(std::shared_ptr<A>(arr[0]));
    arr.push_back(std::shared_ptr<A>(arr[1]));
    //shared_ptr 을 원소로 가지는 백터 arr에 arr[0], arr[1], arr[2] 모두 같은 객체 A를 가리킴
 
    for (int i = 0; i < arr.size(); i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
 
    std::cout << "frist delete!" << std::endl;
    arr.erase(arr.begin());
    std::cout << arr[0].use_count() << std::endl;
 
    std::cout << "second delete!" << std::endl;
    arr.erase(arr.begin());
    std::cout << arr[0].use_count() << std::endl;
 
    std::cout << "third delete!" << std::endl;
    arr.erase(arr.begin());
    //std::cout << arr[0].use_count() << std::endl;
    //참조개수가 3게였다 2, 1, 0 으로 줄어듬 0일때 함수호출시 에러
 
    std::cout << arr.size() << std::endl;
 
 
   //std::shared_ptr<A> p1 = std::make_shared<A>();
    //이 함수는 A의 생성자의 인자를 받아 이를 통해 객체 A와 shared_ptr의 제어블록까지
    //한번에 동적 할당 한 후에 만들어진 shared_ptr을 리턴
 
 
    /*
    A* p = new A();
    std::shared_ptr<A> ptr1(p);
    std::shared_ptr<A> ptr2(p);
    std::cout << ptr1.use_count() << "  " << ptr2.use_count() << std::endl;
    //제어 블록이 2개로 따로 생성됨, 갯수가 1나씩 하지만 소멸자가 두번 호출되는 오류가 발생
    //enable_shared_from_thos 클래스를 사용 -> 래퍼런스 확인
    */
 
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 
 

위 코드를 실행하면 아래의 결과가 나온다.

 

 

벡터에 shared_ptr<>를 가지도록 정의하고 벡터의 0, 1, 2 번째에 모두 같은 A 객체를 가리키도록 생성한다. 그리고 erase함수로 원소들을 하나씩 제거하였다. 

 

shared_ptr 의 경우 객체를 가리키는 모든 스마트 포인터 들이 소멸되어야만 객체를 파괴한다.그래서 마지막 erase로 원소를 지울 때 객체가 소멸하게 되는 것을 볼 수 있다.

 

이를 그림으로 표현하면 아래와 같다.

 

 

위와 같이 표현할 수 있다. 그리고 erase가 하나씩 실행되면서 참조 개수의 값들이 하나씩 감소한다. 

 

위 그림은 위의 예제를 간단히 보여주기 위한 것이고 참조 개수를 제어 블록을 통해 관리한다. 아래그림처럼 이해하면 된다.

 

 

위 그림처럼 실제 객체를 가리키는 shared_ptr  제어 블록(control block) 을 동적으로 할당한 후, shared_ptr 들이 이 제어 블록에 필요한 정보를 공유하는 방식으로 구현된다. 이 제어 블록의 참조 개수가 0이 되면 객체는 소멸하게 된다. 

 

즉 위 그림들을 토대로 shared_ptr 는 복사 생성할 때 마다 해당 제어 블록의 위치를 공유하고, 소멸할 때 제어 블록의 참조 개수를 하나 줄이고, 생성할 때 마다 하나 늘리는 방식으로 작동한다.

 

 

shared_ptr을 생성하는 방법은 간단하다.

1
2
3
std::shared_ptr<A> p1 = std::make_shared<A>();
    //이 함수는 A의 생성자의 인자를 받아 이를 통해 객체 A와 shared_ptr의 제어블록까지
    //한번에 동적 할당 한 후에 만들어진 shared_ptr을 리턴
 

 

위 코드처럼 make_shared<>() 를 사용하여 간단하게 생성할 수 있다.

 

 

shared_ptr을 생성할 때 주의해야 할 점 하나만 알고 마무리하자. 

1
2
3
4
5
6
A* p = new A();
    std::shared_ptr<A> ptr1(p);
    std::shared_ptr<A> ptr2(p);
    std::cout << ptr1.use_count() << "  " << ptr2.use_count() << std::endl;
    //제어 블록이 2개로 따로 생성됨, 갯수가 1나씩 하지만 소멸자가 두번 호출되는 오류가 발생
 
 

 

위 코드처럼 shared_ptr 에 인자로 주소값이 오면, 해당 객체를 첫번째로 소유하는 것처럼 인지한다. 즉 제어 블록이 2개 따로따로 형성된다고 생각하면 된다. 

각각의 제어 블록들은, 다른 제어 블록들의 존재를 모르고 참조 개수를 1 로 설정하게 되고, 만약에 ptr1이 소멸된다면, 참조 카운트가 0 이 되어서 자신이 가리키는 객체 A 를 소멸시키는 문제가 발생한다. 아직 ptr2가 그대로 객체 A를 가리키고 있는데 소멸해버리는 문제가 발생한다.

'C , C++, C#' 카테고리의 다른 글

[C/C++] Callable, std::function  (0) 2020.01.14
[C/C++] weak_ptr  (0) 2020.01.09
[C/C++] unique_ptr  (0) 2019.12.28
[C/C++] iterator (반복자)  (0) 2019.12.11
[C/C++] C++ 17 문법 unpacking(언패킹), 구조체 바인딩  (0) 2019.12.09