본문으로 바로가기

<개요>


이번엔 auto_ptr 부터 weak_ptr 까지 모든 스마트 포인터들에 대해 작성합니다.


<auto_ptr>


auto_ptr은 TR1 부터 생성됐던 스마트 포인터의 시초입니다.

사용자가 깜빡 할 수 있는 동적할당 된 객체의 파괴를 대신 해줄 객체를 만드는 것입니다.

auto_ptr은 C++11 부터 사용 중지 권고, C++17 부터 제거 됐습니다. (현재 vs2017 내에선 살아있습니다;)


1
2
3
4
5
6
7
8
9
10
11
    int* b = new int;
    b = 0;
 
    auto_ptr<int>ptr1(b);    // b의 소유권은 ptr1에게 있습니다.
    auto_ptr<int>ptr2(new int);    //RAII 처럼 이런 방식도 가능!
    auto_ptr<int>ptr3 = ptr1;    // b의 소유권은 이제 ptr3 에게로, ptr1은 b에 대한 소유권을 가지지 않습니다.
    auto_ptr<int>ptr4 = ptr2;    // ptr2의 동적할당객체에 대한 소유권도 ptr4에게 넘어갔습니다.
 
 
    auto_ptr<int> ptr5;
    ptr5 = b;    // 에러! 대입연산은 소유권 이전을 위해서 입니다. 생성자를 호출하여야 됩니다.
cs


auto_ptr은 기본적으로 배열을 사용하지 않는 동적할당에서, 한 개체값에 대해서만 사용해야 합니다.

무슨말인고하니, auto_ptr은 동적할당된 배열을 삭제시키지 않습니다.


1
2
    auto_ptr<int> A(new int[10]); // 1번째부터의 요소에 접근할 수 없습니다.
    // 또한 auto_ptr은 delete를 [] 로 하지 않습니다.
cs

A는 int의 0번째 배열에 대한 정보를 가지고 있을 뿐, 1번째부턴 접근할 수 있는 방법이 없습니다. ( (*A) + 1 이런 식으로 접근은 가능합니다만은... )

1
2
3
~auto_ptr() {
    delete _Myptr;
}
cs

auto_ptr의 소멸자는 이런식으로 구현이 돼있습니다. 배열 단위의 해제는 해주지 않습니다.

또한 위의 예제와 같이 auto_ptr은 값 단위 복사를 할 수 없습니다. 단지 소유권 이동입니다.
위의 상황때문에 auto_ptr은 사용 중지 권고가 됐고, 대신 더 강력하고 auto_ptr을 대체할 unique_ptr이 등장했습니다.

<unique_ptr>

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
class unique_ptr {
public:
    unique_ptr();
    unique_ptr(nullptr_t Nptr);
    explicit unique_ptr(pointer Ptr);
    unique_ptr(pointer Ptr,
        typename conditional<is_reference<Del>::value, Del,
        typename add_reference<const Del>::type>::type Deleter);
    unique_ptr(pointer Ptr,
        typename remove_reference<Del>::type&& Deleter);
    unique_ptr(unique_ptr&& Right);
    template <class T2, Class Del2>
    unique_ptr(unique_ptr<T2, Del2>&& Right);
    unique_ptr(const unique_ptr& Right) = delete;
    unique_ptr& operator=(const unique_ptr& Right) = delete;
};
 
//Specialization for arrays:
template <class T, class D>
class unique_ptr<T[], D> {
public:
    typedef pointer;
    typedef T element_type;
    typedef D deleter_type;
    constexpr unique_ptr() noexcept;
    template <class U>
    explicit unique_ptr(U p) noexcept;
    template <class U>
    unique_ptr(U p, see below d) noexcept;
    template <class U>
    unique_ptr(U p, see below d) noexcept;
    unique_ptr(unique_ptr&& u) noexcept;
    constexpr unique_ptr(nullptr_t) noexcept : unique_ptr() { }     template <class U, class E>
        unique_ptr(unique_ptr<U, E>&& u) noexcept;
    ~unique_ptr();
    unique_ptr& operator=(unique_ptr&& u) noexcept;
    template <class U, class E>
    unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;
    unique_ptr& operator=(nullptr_t) noexcept;
    T& operator[](size_t i) const;
 
    pointer get() const noexcept;
    deleter_type& get_deleter() noexcept;
    const deleter_type& get_deleter() const noexcept;
    explicit operator bool() const noexcept;
    pointer release() noexcept;
    void reset(pointer p = pointer()) noexcept;
    void reset(nullptr_t = nullptr) noexcept;
    template <class U>
    void reset(U p) noexcept = delete;
    void swap(unique_ptr& u) noexcept;  // disable copy from lvalue unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&= delete;
};
cs

unique_ptr은 위와 같이 구성돼있습니다.
unique_ptr을 효과적으로 생성하려면 make_unique 도우미 함수를 이용하면 됩니다. ( make_unique는 내부적으로 동적 할당을 실행합니다! )
get 함수는 현재 unique_ptr이 가지고 있는 객체를 불러옵니다.

1
2
3
4
5
6
7
8
9
10
int* temp = new int;
*temp = 3;
 
unique_ptr<int> a(temp);
unique_ptr<int> b(new int);
auto c = make_unique<int>(5);
unique_ptr<int> d = a;    // error, 대입 연산은 지원하지 않습니다.
unique_ptr<int> f = std::move(a);    //ok, move semantics는 지원합니다. 이 결과 a는 소유권을 잃습니다.
unique_ptr<int[]> g(new int[10]);
g[0] = 10;    // g의 0번째 배열에 10을 넣습니다.
cs


unique_ptr로 배열을 감싸려면 꼭 unique_ptr<int[]> 로 선언하셔야 합니다!!!

이렇게 선언하지않으면 내부에선 delete _Myptr; 을 호출합니다.


reset 함수는 객체를 파괴시키고 빈 값을 가집니다.
하지만 reset 함수와 동시에 값을 집어 넣을 수 있습니다.

1
2
g.reset();    // g는 int[10] 을 파괴하고 빈 값을 가지게 됩니다.
f.reset(new int(5));    // f는 a를 파괴 시키고, 다시 새로운 5 값을 넣습니다.
cs

release 함수는 객체를 파괴시키지 않고, 포인터만 뱉어내고 unique_ptr을 null로 만듭니다.
이 결과 뱉어낸 포인터의 소멸자는 사용자가 직접 호출해주어야 합니다.

1
2
3
unique_ptr<int> a(new int);
int* b = a.release();
delete b;
cs


swap 함수는 두 unique_ptr 간의 포인터(객체) 를 교환합니다.

1
2
3
    unique_ptr<int> a(new int(5));
    unique_ptr<int> b(new int(10));
    a.swap(b);    // a와 b의 객체를 교환합니다. 이결과 a는 10, b는 5
cs

응용하여 unique_ptr의 factory 함수를 만들어 낼 수도 있습니다.

1
2
3
4
5
6
7
8
9
template <typename T>
unique_ptr<T> getallocator(const T& rhs) {
    return make_unique<T>(rhs);
}
 
int main() {
    auto A = getallocator<int>(5);
    cout << *<< endl; // 5
}
cs

마찬가지로 vector, list등 컨테이너에도 사용 가능합니다.

1
2
3
4
5
6
7
8
9
10
vector<unique_ptr<int>> A;
A.push_back(make_unique<int>(5));
A.push_back(make_unique<int>(10));
 
cout << *(A[0]) << endl// 5
cout << *(A[1]) << endl// 10
 
for (const auto& i : A) {
    cout << *<< endl;        // 5, 10
}
cs

<shared_ptr>

shared_ptr은, unique_ptr 에서 각 스마트 포인터들이 같은 값을 가지지 못하는 것을 보안하기 위해 등장했습니다.
내부에선 참조 카운팅 방법을 사용하며, 0이 되면 삭제됩니다. 
shared_ptr 또한 make_shared 라는 도우미 함수가 존재합니다.
기본적으론 unique_ptr의 함수와 동일하므로, 다른 함수만 작성하겠습니다.

1
2
3
4
5
6
int main() {
    shared_ptr<int> a(new int(5));
    shared_ptr<int> b = a;    
    cout << *<< endl;    //5
    cout << *<< endl;    //5
}
cs

shared_ptr a가 객체를 가지게 되면, a는 count를 1 증가 시킵니다.
만약 대입이 일어나 b가 a 와 같은 값을 가지게 되면, count를 1 증가시켜 2로 바꿉니다.
그렇게 a 와 b 의 소멸자가 호출이 된다면, count 가 1이 아니면 count 를 감소만 시켜주고 소멸자를 빠져 나옵니다.
만약 a가 제거 됐다면 count 는 다시 1로 줄어들 것이고, b가 제거되면서 count 가 1이니 객체인 new int(5) 를 삭제해주는 방식입니다.

use_count 함수는 현재 shared_ptr이 가지고 있는 객체의 참조 카운터가 몇인지 알려줍니다.

1
2
3
shared_ptr<int> a(new int(5));
shared_ptr<int> b = a;    
cout << a.use_count() << endl;    // 2
cs

unique 함수는 현재 객체가 고유한지 알려줍니다.

2
3
4
5
6
7
shared_ptr<int> a(new int(5));
shared_ptr<int> b = a;    
shared_ptr<int> c(new int(10));
 
cout << a.unique() << endl;    // false
cout << b.unique() << endl;    // false
cout << c.unique() << endl;    // true
cs

위 결과 a 는 b 와 똑같은 객체를 가르키고 있기 때문에 false 가 나옵니다. ( use_count를 사용하는 방식 )
shared_ptr은 비멤버 함수로 pointer의 형변환을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<class _Ty1, class _Ty2>
shared_ptr<_Ty1> static_pointer_cast(const shared_ptr<_Ty2>& _Other)
{      
        // return shared_ptr object holding static_cast<_Ty1 *>(_Other.get())
        return (shared_ptr<_Ty1>(_Other, _Static_tag()));
}
 
template<class _Ty1, class _Ty2>
shared_ptr<_Ty1> const_pointer_cast(const shared_ptr<_Ty2>& _Other)
{      
        // return shared_ptr object holding const_cast<_Ty1 *>(_Other.get())
        return (shared_ptr<_Ty1>(_Other, _Const_tag()));
}
 
template<class _Ty1, class _Ty2>
shared_ptr<_Ty1> dynamic_pointer_cast(const shared_ptr<_Ty2>& _Other)
{      
        // return shared_ptr object holding dynamic_cast<_Ty1 *>(_Other.get())
        return (shared_ptr<_Ty1>(_Other, _Dynamic_tag()));
}
 
cs

1
2
3
4
5
shared_ptr<int> a(new int(5));
 
shared_ptr<double> b = static_pointer_cast<double>(a);    // a의 값을 double로 형변환하여 b에 넣어줌
// 이 경우 대입이기때문에 reference count 는 2로 증가합니다!!
static_pointer_cast<double>(a);        // a를 int에서 double로 형변환함
cs


shared_ptr은 순환참조의 문제점이 있기 때문에 각별히 유의해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {
public:
    A(int _id) : ID(_id) {}
    shared_ptr<A> m_other;
    int ID;
};
 
int main() {
    shared_ptr<A> myA(new A(1));
    shared_ptr<A> myB(new A(2));
 
    myA->m_other = myB;
    myB->m_other = myA;
 
    myA.reset();
    // myA는 사라졌으나, myB->m_other가 가지고있는 myA의 A(1) 객체가 아직도 남아있다.
    // 그러므로 실질적인 A(1)은 아직도 살아있는 상태
}
cs


대부분 순환참조는 shared_ptr이 가지고 있는 객체의 멤버가 shared_ptr이 있을때 나타나게 되는데, 
위처럼 멤버변수가 다른 shared_ptr을 참조하는 경우이다.
이 경우, myA는 empty가 되지만 myA가 가지고 있는 A(1) 객체는 사라지지 않는다.
거기서 만약 myB의 m_other이 다른 객체를 참조하게 된다면 A(1)은 영원히 존재하게 된다.

위의 순환 참조를 없애기 위해 나온 대안이 weak_ptr 이다.

<weak_ptr>

weak_ptr은 shared_ptr을 참조할 수 있게 해주는 스마트 포인터로써, 참조는 하지만 reference count는 증가시키지 않는다.
위와 같은 순환 참조를 막는데 주로 사용된다.

1
2
3
4
shared_ptr<int> a(new int(1));
weak_ptr<int> b = a;
 
cout << a.use_count() << endl;    // 1
cs

위 처럼 a 의 reference count는 1 이 된다.
( 여담 : reference count 에도 strong 과 weak가 존재한다고 한다. shared가 shared를 참조하면 strong count가 올라가고
weak가 weak 또는 shared를 참조하면 weak count가 올라간다고 한다 . 불론 weak count는 shared의 소멸자에 영향을 주지 않음 
내용 : http://egloos.zum.com/sweeper/v/3059940 )

weak_ptr은 그 자체만으로는 shared_ptr의 값에 접근하지 못한다.
접근하려면 lock 함수를 사용해 shared_ptr로 convert 한 후 접근하여야 한다.
expired 함수는 자신이 참조하고있는 shared_ptr이 존재 하는가에 대해 반환한다.

1
2
3
4
5
6
7
shared_ptr<int> a(new int(1));
weak_ptr<int> b = a;
 
cout << *<< endl;    //error
cout << *(b.lock()) << endl;    //1
    
cout << b.expired() << endl;    //false
cs

expired는 존재하지않는다면 true를, 존재한다면 false를 리턴한다.

음 대충 작성한 것 같은데, 더 붙힐게 생각나거나 보완해야할 점이 보이면 바로바로 수정하겠다.