프로그래밍 언어/C/C++

[C++11] unique_ptr

소혼 2013. 11. 1. 23:40
반응형
목차로 가기


<개인적으로 공부한 내용들로, 내용이 완벽하지 않을 수 있습니다. 부족한 부분/틀린 부분에 대한 Comment 환영합니다.>


unique_ptr는 C++에서 제공하는 스마트 포인터입니다.

(TODO: 스마트 포인터에 대한 설명 추가)


unique_ptr를 사용하면 동적으로 힙 영역에 할당된 인스턴스에 대한 소유권을 얻고 unique_ptr 객체가 사라지면 해당 인스턴스를 해제하게 됩니다.

소유권을 갖기 때문에 인스턴스를 소유하는 unique_ptr은 동시에 두개가 되어선 안됩니다.

(둘이 된 경우 소멸되는 과정에서 double free 에러가 발생하게 될 것입니다.)


unique_ptr를 사용하려면 <memory> 헤더를 인클루드하면 됩니다.


간단한 unique_ptr의 사용 예제입니다.

### c++

#include <iostream>
#include <memory>

using namespace std;
void f()
{  
    unique_ptr<int> a(new int(3));
    cout << *a.get() << endl;
}

int main()
{  
    f();
}

a는 f()함수가 사라짐과 동시에 소멸됩니다. 사용자가 굳이 delete를 해줄 필요가 없고 unique_ptr<int> 객체인 a가 자신의 소멸 시점에  할당된 메모리를 제거할 것입니다.

예제에서는 unique_ptr의 메소드중 하나인 get()을 이용해 할당된 메모리를 얻어 출력하고 있습니다.


a를 선언하는 방법은 아래와 같이 할 수도 있습니다.

### c++

unique_ptr<int> a = unique_ptr<int>(new int(3));


아래와 같이 사용하는 것도 가능합니다.

### c++
unique_ptr<int> factory()
{  
    return unique_ptr<int>(new int(3));
}

void f()
{
    unique_ptr<int> a = factory();
    cout << *a.get() << endl;
}


하지만, 아래와 같이 대입을 하는 것은 불가능합니다.

### c++

unique_ptr<int> a = unique_ptr<int>(new int(3));
unique_ptr<int> b = a; // error!!


이 경우 아래와 같은 에러가 발생합니다. (gcc 기준)

$ g++ unique_ptr.cpp -std=c++11
unique_ptr.cpp: In function ‘void f()’:
unique_ptr.cpp:14:25: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>; std::unique_ptr<_Tp, _Dp> = std::unique_ptr<int>]’
In file included from /usr/include/c++/4.7/memory:86:0,
                 from unique_ptr.cpp:2:
/usr/include/c++/4.7/bits/unique_ptr.h:262:7: error: declared here


std::move

데이터를 이동시키고자 할때는 아래 예제처럼 std::move를 사용해야 합니다.

### c++

void f()
{
    unique_ptr<int> a(new int(3));
    unique_ptr<int> b = move(a);
    cout << *b.get() << endl;
    //cout << *a.get() << endl; //Segmentation Fault!
}

move를 호출하면 a가 소유하고 있는 객체(new int(3))는 b에게 넘어갑니다. 따라서 a는 empty 상태가 되고 이러한 a를 통한 접근은 segmentation fault를 야기하게 됩니다.


위에서 간단히 int를 할당하여 int의 raw pointer를 얻는 법을 계속 보여드렸습니다. 하지만 이런 접근은 그다지 안전하다고 볼 수 없습니다.

unique_ptr에 객체를 할당하고 객체의 메소드를 사용하는 것이 더 일반적인 사용법이라고 생각됩니다. 이 때는 기존에 직접 할당했을 때와 마찬가지로 ->를 사용하여 접근할 수 있습니다. 간단한 예제를 만들어보았습니다.

### c++

#include <iostream>
#include <memory>

using namespace std;
class house
{
public:
    house(int window, int door)
        : m_window(window)
        , m_door(door)
    { }
   
    int window() const { return m_window; }
    int door() const { return m_door; }

private:
    int m_window;
    int m_door;
};

int main()
{
    unique_ptr<house> home(new house(4, 1));
    cout << home->window() << " windows and " << home->door() << " doors" << endl;

    return 0;
}

home은 house의 객체가 아니라 unique_ptr<house>의 객체입니다. 하지만 window()나 door()와 같은 house의 메소드에 접근할 수 있습니다. (사실은 unique_ptr이 ->를 오버라이드하는 것으로 보는 것이 더 정확합니다.)


std::unqie_ptr::reset

만약 unique_ptr에 할당된 값을 지우거나 다른 값으로 바꾸고 싶다면 reset이란 메소드를 사용하면 됩니다.


위 예제에서 main부분만을 아래와 같이 변경해보겠습니다.

### c++

int main()                                                                         
{                                                                                  
    unique_ptr<house> home; //empty                                                
                                                                                   
    home.reset(new house(2, 1));                                                   
    home.reset(new house(4, 1));                                                   
    home.reset();
                                                                                   
    home.reset(); //nothing happend                                                
                                                                                   
    return 0;                                                                      
}

home 객체는 empty 로 생성되었습니다. 하지만 5번 reset을 통해 house(2, 1)의 ownership을 갖게 됩니다.

6번 reset을 수행하면 위에서 생성된 house(2,1)은 소멸되고 house(4, 1)의 ownership을 갖게 됩니다.

7번 라인과 같이 인자 없이 수행하면 갖고 있는 객체를 소멸시키게 됩니다.


이 동작은 9번 라인과 같이 현재 객체가 없는 경우에도 아무런 문제가 없습니다.


std::unique_ptr::release

현재 소유한 인스턴스의 소유권을 해제 없이 놓고자 할때는 release라는 메소드를 사용합니다.

이 경우, 반환된 인스턴스를 직접 해제해 주어야 합니다.


만약 현재 unique_ptr객체가 empty였다면, 반환된 인스턴스는 nullptr이 될 것입니다.

### c++

int main()
{
    unique_ptr<house> home; //empty

    house *p = home.release();
    if (p)
        cout << "It's not printed" << endl;
    home.reset(new house(4, 1));

    p = home.release();
    if (p) {
        cout << "p should be released" << endl;
        delete p;
    }

    return 0;
}


custom deleter

이미 알고 계시겠지만, unique_ptr은 메모리의 할당에는 관여하지 않으면서 메모리의 해제를 수행합니다, 따라서 만약 메모리를 new가 아니라 malloc과 같이 다른 형태로 할당했다면 문제가 발생할 것입니다. 이때는 custom deleter를 구현해주어야 합니다.


아래는 malloc으로 할당하고 custom deleter를 통해 해제하는 예입니다.

### c++

struct my_free
{
    void operator()(void* x) { free(x); }
};

int main()
{
    unique_ptr<int, my_free> a((int*)malloc(sizeof(int)));

    return 0;
}


추가정보1. WebKit에서는 OwnPtr이란 것을 사용했지만, c++11을 사용할 수 있게 된 이후, 현재 unique_ptr로 변경하는 추세입니다.

추가정보2 : c++14 에서는 unique_ptr의 factory인 make_unique가 도입될 듯 합니다.


참조 (custom deleter) : http://stackoverflow.com/questions/3477525/is-it-possible-to-use-a-c-smart-pointers-together-with-cs-malloc

참조 : http://en.wikipedia.org/wiki/Smart_pointer#C.2B.2B_smart_pointers


반응형

'프로그래밍 언어 > C/C++' 카테고리의 다른 글

[c++11] rvalue reference  (1) 2013.11.07
[C++11] Variadic template  (0) 2013.11.06
[C++11] Type Inference: auto  (4) 2013.11.01
c++11 스터디(목차)  (0) 2013.11.01
constructor/destructor에서 virtual method의 호출  (0) 2013.02.27