[C++] 독점적으로 자원을 관리하는 std::unique_ptr 객체

반응형

std::unique_ptr

std::unique_ptr은, C++ 11에, 할당된 메모리를 편리하게 관리할 목적으로 도입된 스마트 포인터( smart pointer ) 클래스입니다.

 

unique_ptr 객체가 하는 일은, 메모리가 할당된 객체의 주소를 보관했다가, 이 스마트 포인터가 파괴될 때, 이 주소를 사용해서, 할당된 객체를 파괴하고, 이때 사용되었던 메모리를 시스템에 반환하는 것입니다.

 

이 클래스를 사용하기 위해선 먼저 다음의 헤더 파일을 포함해야 합니다.

#include <memory>

 

아래는 unique_ptr 객체를 선언하는 방법을 보여줍니다.

#include <iostream>
#include <memory>
using namespace std;

#define SIZE    100

// 데이터를 할당하는 클래스
class ResourceObj{
    int* m_pData;
public:
    ResourceObj(){
        m_pData = new int[SIZE];
        cout << "Resource Created\n";
    };
    ~ResourceObj(){
        delete [] m_pData;
        cout << "Resource Destory\n";
    };
};

int main(){

    ResourceObj* pObjPtr = new ResourceObj();
    
    // unique_ptr의 기본 사용법
    std::unique_ptr<ResourceObj> pRes { new ResourceObj() };
    
    delete pObjPtr;	// 할당된 객체를 삭제
}

보통, 객체에 메모리를 할당할 때는, pObjPtr 같은 포인터에 객체의 주소를 저장했다가, 객체를 다 사용하게 되면, 이 포인터를 이용해서 객체를 파괴하고, 할당된 메모리를 다시 시스템에 반환해야 합니다.

 

위의 unique_ptr 객체인 pRes도, 이와 마찬가지로, 메모리가 할당된 ResourceObj 타입의 객체 주소로 초기화됩니다.

그렇지만, 이 pRes 객체가 범위( scope )를 벗어나서 파괴될 때, 할당된 ResourceObj 객체 또한 자동으로 파괴하고, 사용되었던 메모리를 시스템에 반환합니다.

그렇기 때문에, delete pObjPtr; 문장과 같은, 할당된 ResourceObj 객체를 삭제하는 코드를 따로 작성할 필요가 없습니다.

 

이러한 unique_ptr 객체는 할당된 배열을 관리할 수도 있습니다.

#define SIZE 10

int main(){

    unique_ptr<int[]> ptr{ new int[SIZE] };	// 배열 객체 관리
    
    for( int i = 0; i < SIZE; i++){
        ptr[i] = 3;	// [] 연산자 사용
    }
}

위와 같이, unique_ptr 클래스는 [] 연산자를 지원하기 때문에, ptr[i]처럼, 인덱스를 통해서 관리하고 있는 배열에 접근할 수 있습니다.

게다가, 위의 ptr 객체가 파괴될 때, 이 객체가 관리하고 있는 데이터가 배열인 경우 ( delete 연산자가 아니라 ) delete [] 연산자를 호출하여, 자동으로 배열을 삭제합니다.

 

이러한 스마트 포인터를 사용하는 주된 이유는, 위에도 얘기했지만, 크게 신경 쓰지 않고 할당된 객체를 삭제하고, 이 객체에 사용된 메모리를 시스템에 반환할 수 있다는 것입니다.

그리고, 할당된 메모리에 관한 문제는 크게 메모리 누수( memory leak )와 삭제된 메모리를 다시 삭제하는 문제( double free )로 나눠 볼 수 있는데, 이러한 문제들을 해결하는데도 크게 도움이 되기 때문입니다.

 

메모리 누수( memory leak )

메모리 누수( memory leak )는 할당된 메모리를, 다 사용한 후에도, 시스템에 반환하지 못하는 문제를 말합니다.


다음 코드는 정상적인 메모리 사용예입니다.

#define SIZE    100

// 데이터를 할당하는 클래스
class ResourceObj{
    int* m_pData;
public:
    ResourceObj(){
        m_pData = new int[SIZE];
        cout << "Resource Created\n";
    };
    ~ResourceObj(){
        delete [] m_pData;
        cout << "Resource Destory\n";
    };
};

void DoSomething(){
       
    ResourceObj* pRes = new ResourceObj();
    // do something...
    
    delete pRes;	// 다 사용후, 메모리 해제
}

int main(){
    
    DoSomething();
}

▼출력

Resource Created
Resource Destory

위 코드를 실행하면 ResourceObj 객체에 할당한 메모리가 제대로 반환되는 것을 볼 수 있습니다.

그러나, 다음과 같은 경우는 메모리가 제대로 반환되지 않습니다.

void DoSomething(){
    
    ResourceObj* pRes = new ResourceObj();

    throw(1);   // Exception    
    
    delete pRes;	// 이 경우 실행되지 않음
}

int main(){
    
    try{
        // Do something and Throw exception
        DoSomething();
    }
    catch(int e){
        cout << "Exception occured\n";
    }
}

▼출력

Resource Created
Exception occured

왜냐하면, DoSomething 함수 내에서 예외가 발생했기 때문입니다.

 

[C++] 예외 처리를 위한 throw와 try-catch의 동작 방식

C++에서의 예외( exception ) 처리C++에서는 프로그램의 오류나 기대하지 못했던 상황이 발생한 경우, 이를 처리하기 위한 예외( exception )를 발생시키면, 프로그램이 자동으로, 이 예외를 처리할 수

codingbonfire.tistory.com

이 경우, 실행 제어( execution control )는 예외가 발생한 직후, 함수를 벗어나 main 함수의 예외 처리를 수행합니다.
그래서, 할당한 ResourceObj 객체를 파괴할 기회를 놓치게 되고, 메모리는 누수됩니다.

 

하지만, 이런 경우에 unique_ptr를 사용하면 어떻게 될까요?

void DoSomething(){
    
    unique_ptr<ResourceObj> pRes { new ResourceObj() };

    throw(1);   // Exception    
}

int main(){
    
    try{
        // Do something and Throw exception
        DoSomething();
    }
    catch(int e){
        cout << "Exception occured\n";
    }
}

▼출력

Resource Created
Resource Destory
Exception occured

이 경우는 지역변수인 unique_ptr 객체가 먼저 파괴되고, 그다음 예외 처리가 실행됩니다.
그리고 이 과정에서, 이 unique_ptr가 관리하는 ResourceObj 객체를 파괴하고, 할당된 메모리를 반환하므로, 메모리 누수 문제는 발생하지 않습니다.

 

참고로, 함수가 호출될 때, 호출한 함수의 호출 주소 및 로컬 변수 등의 정보가 스택에 저장됩니다.
이것을 스택 와인딩( stack winding )이라고 합니다.

그런데 만약, 함수가 종료되거나 예외( exception ) 발생 시 그냥 함수를 벗어나면, 이 스택에 들어있는 정보들이 쌓이게 되는 문제가 발생합니다.
그래서, 함수를 벗어나기 전엔 항상 스택을 비우게 되는데, 이 과정을 스택 언와인딩( stack unwinding )이라고 합니다.

이 과정에서, 스택에 할당된, 지역 변수인 unique_ptr 객체도 파괴됩니다.

 

삭제된 메모리 삭제( double free )

객체에 할당된 메모리는, 메모리를 할당할 때 구한 메모리 주소를 사용하여, 반드시 한 번만 시스템에 반환해야 합니다.

그런데, 이렇게 메모리가 반환된 후에도, 다시 반환하려고 시도하는 것을 중복 삭제( double free )라고 합니다.

void deleteObject( ResourceObj* pObj ){
    delete pObj;    // 객체 삭제
}

int main(){

    ResourceObj* pObj = new ResourceObj();
    deleteObject( pObj);

    delete pObj;    // double free. error !!
}

 

이 문제를 해결하려면 먼저 변수의 소유권의 개념을 이해해야 합니다.

변수의 소유권이란, 변수가 생성되고 파괴될 때를 결정하는 권한을 말합니다.
예를 들면, 함수의 지역 변수는 함수가 시작될 때 생성되고, 함수가 종료될 때 같이 파괴됩니다.
따라서, 함수가 이 지역 변수의 소유권을 갖고 있습니다.

또, if 구문의 {} 안의 변수의 소유권은 이 if {} 코드 블록이 갖고 있습니다.

이러한 코드 블록 안의 변수는, 이 범위를 벗어나면 파괴됩니다.

if ( true ){	
    int local_variable{ 3 };
}


그리고, 위 예문의 ResourceObj의 데이터 멤버 m_pData의 소유권은 ResourceObj 객체가 갖고 있습니다.

ResourceObject 객체가 삭제될 때, 이 변수도 삭제되기 때문입니다.

마찬가지로, 아래의 unique_ptr 객체 pRes는 할당된 ResourceObj 객체의 소유권을 갖고 있습니다.

unique_ptr<ResourceObj> pRes { new ResourceObj() };

 

그런데 다음과 같이, 이 소유권을 복사할 수 있다면 어떻게 될까요?

int main(){

    unique_ptr<ResourceObj> pRes { new ResourceObj() };

    unique_ptr<ResourceObj> pCopy = pRes;   // unique_ptr 복사?
}

그러면, pCopy가 삭제될 때, 이 객체가 보관하고 있는 ResourceObj가 삭제될 것이고, 그다음 pRes가 삭제할 때도, 이 pRes가 보관하고 있던, 같은 ResourceObj를 다시 삭제하려고 시도할 것입니다. double-free죠.

 

그렇지만, 다행히 이러한 일은 발생하지 않습니다.
unique_prt 클래스는, 아래와 같은 방법으로, 복사 생성자( copy construct )와 복사 대입 연산자( copy assignment )를 삭제했기 때문입니다.

unique_ptr(const unique_ptr&) = delete;

위의 = delete는 C++11에 도입된 기능으로, 명시적으로 이렇게 선언된 함수는 사용할 수 없다는 뜻입니다.

 

여기서, 뭔가 이상하다는 것을 느낄 수도 있습니다.

왜냐하면, 사용하지 않을 생성자를 왜 굳이 선언한 후, 위와 같이 "사용할 수 없다"라고 명시해야 하는지 이해할 수 없기 때문입니다.

 

그런데, 복사 생성자와 복사 대입 연산자는 개발자가 제공하지 않으면 컴파일러가 자동으로 만들어 사용합니다.

그래서, ( 소유권을 복사하지 못하도록 ) 위와 같이 생성자를 명시적으로 삭제하는 과정이 필요한 것입니다.

 

이 과정을 통해, unique_ptr 객체는 관리하고 있는 객체에 대한 소유권을 유일하게 갖게 되고, 이것이 이 스마트 포인터( smart pointer )의 이름을 unique_ptr로 명명한 이유입니다.

 

만약, 다음과 같이 unique_ptr 클래스의 복사 생성자를 호출하면 오류 메시지를 만나게 될 것입니다.

unique_ptr<ResourceObj> pCopy = pRes;

▼출력

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&)

 

이렇게 해서, 삭제된 메모리를 다시 삭제하는 문제( double free )도 해결되었습니다.

 

참고로, unique_ptr 객체의 소유권을 복사하는 것은 되지 않지만, 양도는 가능합니다.
그리고, 소유권을 양도한 객체는 당연히 그전에 관리하던 객체에 대한 접근 권한을 잃어버리게 됩니다.

int main(){
    
    unique_ptr<ResourceObj> pRes { new ResourceObj() };

    unique_ptr<ResourceObj> pReceiver = std::move(pRes);	// 소유권 양도

    if ( pRes ){	// 관리하는 객체가 있는 경우 true
        cout << "pRes has previlege\n";
    }

    if ( pReceiver ){
        cout << "pReceiver has previlege\n";
    }
}

▼출력

Resource Created
pReceiver has previlege
Resource Destory

unique_ptr 클래스는, 위의 if( pRes ) 코드와 같이, bool 타입으로 변환할 수 있는 연산자를 제공합니다.

이 연산자는 unique_ptr 객체가 관리하는 객체가 있는 경우 true를, 그렇지 않다면 false를 반환합니다.

 

 

std::unique_ptr에 의해 관리되는 객체에 접근

unique_ptr 클래스는 이 타입의 객체에 의해 관리되는 객체에 접근할 때 사용할 연산자들을 제공합니다.

-> 연산자는 unique_ptr 객체에 관리되는 객체의 포인터( pointer )를 반환하고, * 연산자는 객체의 참조( reference )를 반환합니다.
또한, 제공되는 bool 연산자는 unique_ptr 객체에 의해 관리되는 객체가 있는 경우 true를, 그러한 객체가 없는 경우 false를 반환합니다.

다음 예문을 통해, 관리하는 객체에 접근하는 방법을 볼 수 있습니다.

#define SIZE    100

class ResourceObj{	
    int* m_pData;
public:
    ResourceObj(){
        m_pData = new int[SIZE];
        cout << "Resource Created\n";
    };
    ~ResourceObj(){
        delete [] m_pData;
        cout << "Resource Destory\n";
    };

    bool SetData(int idx, int val){	// 데이터의 값 변경
        if ( m_pData == nullptr || idx >= SIZE ) return false;
        m_pData[idx] = val;
        return false;
    }
};

int main(){
    
    unique_ptr<ResourceObj> pRes { new ResourceObj() };

    if ( pRes){	// 관리하고 있는 데이터가 있으면 true
    
        pRes->SetData( 10, 3);	// -> 연산자를 통해 관리하는 데이터에 접근
    }
}

unique_ptr을 스마트 포인터( smart pointer )라고 하는 이유는, 위에서 볼 수 있듯이, 포인터 연산자와 형태가 같고, 사용법도 같기 때문입니다.

 

std::unique_ptr의 초기화

관리하는 객체를 갖고 있는 unique_ptr 객체를 초기화하기 위해 releasereset 멤버 함수를 호출할 수 있습니다.

 

먼저 release 멤버 함수는 unique_ptr 객체가 관리하고 있던 객체에 대한 소유권을 포기하는 것과 동시에 그 객체에 대한 포인터를 반환합니다.

int main(){

    unique_ptr<ResourceObj> pRes{ new ResourceObj()};
    
    ResourceObj* pObj = pRes.release();
    delete pObj;
    
    //pRes.reset();

    if ( pRes ){
        std::cout << "pRes has ResourceObj\n";
    }
    else{
        std::cout << "pRes has no object\n";
    }
]

▼출력

Resource Created
Resource Destory
pRes has no object

그리고, 반환된 포인터는 더 이상 자동으로 삭제되지 않으므로, 메모리 누수( memory leak )를 막기 위해서, 이 포인터를 삭제하는 코드를 추가로 작성해야 합니다.

 

release 함수와 비슷한, reset 함수는 관리하고 있던 객체를 파괴하고, 새로운 객체를 관리하도록 설정하거나, 아무런 객체를 관리하지 않도록 만들 수 있습니다.

int main(){

    unique_ptr<ResourceObj> pRes{ new ResourceObj() };

    pRes.reset();	// reset

    if ( pRes ){
        std::cout << "pRes has ResourceObj\n";
    }
    else{
        std::cout << "pRes has no object\n";
    }
}

▼출력

Resource Created
Resource Destory
pRes has no object

 

std::unique_ptr 객체를 함수에 전달

unique_ptr 객체는 복사할 수 없으므로, 이 객체를 함수에 값으로 전달( call by value )하려면, 아래와 같이 이동 연산을 해야 합니다.

void func( unique_ptr<ResourceObj> pObj ){
    // do somethng...
}

int main(){

    unique_ptr<ResourceObj> pRes{ new ResourceObj()};
    
    func( std::move( pRes ) ); // 함수에 소유권 전달
    
    if ( pRes ){
        std::cout << "pRes has ResourceObj\n";
    }
    else{
        std::cout << "pRes has no object\n";
    }
}

▼출력

Resource Created
Resource Destory
pRes has no object

그리고, ResourceObj] 객체의 소유권을 전달받은, func 함수의 pObj 매개변수는, 함수 종료 시 파괴될 때, 관리하던 ResourceObj 객체를 파괴합니다.

 

하지만, 함수 내에서 전달받은 객체를 파괴하는 방식은 프로그램의 구조를 파악하기 힘들게 만듭니다.

그러므로, 소유권이 넘어가지 않도록 const unique_ptr& 타입의 매개변수를 사용할 수도 있습니다.

void func( const unique_ptr<ResourceObj>& pObj ){
    // do somethng...
}

int main(){

    unique_ptr<ResourceObj> pRes{ new ResourceObj()};
    func( pRes );	// 이동 연산을 할 필요없음
}

이런 타입의 매개 변수를 사용하면, unique_ptr 객체가 관리하는 ResourceObj 객체는 파괴되지 않습니다.

그렇지만, 이러한 함수 타입보다는 아래와 같은 타입의 매개변수를 갖는 함수가 좀 더 유연하다고 할 수 있습니다.

ResourceObj 타입의 객체에 대해서도 이 함수를 호출할 수 있기 때문입니다.

void func( const ResourceObj& pObj ){	// const ResourceObj& 타입 매개변수
    // do somethng...
}

int main(){

    unique_ptr<ResourceObj> pRes{ new ResourceObj()};
    func( *pRes );  // * 연산자 사용
    
    if ( pRes ){
        std::cout << "pRes has ResourceObj\n";
    }
    else{
        std::cout << "pRes has no object\n";
    }
}

▼출력

Resource Created
pRes has ResourceObj
Resource Destory

 

함수에서 std::unique_ptr 객체를 반환

함수에서 unique_ptr 타입의 객체를 반환할 때는 값으로 반환( return by value ) 해야 합니다.

함수 종료 시, 관리하고 있던 객체의 소유권을 이전해야 하기 때문입니다.

만약 unique_ptr의 포인터 타입이나, 참조 타입을 반환하면, 관리하고 있던 객체는 함수 내에서 파괴되므로, 정의되지 않는 동작을 초래하게 됩니다.

 

아래 함수는 함수 종료 시, unique_ptr<ResourceObj> 타입의 객체를 반환합니다.

unique_ptr<ResourceObj> func(){	// unique_ptr 객체 반환
    
    unique_ptr<ResourceObj> pObj{ new ResourceObj()};
    // do something...
    return pObj;	
}

int main(){

    unique_ptr<ResourceObj> pRes{ func() };	// 소유권의 이동
    
    if ( pRes ){
        std::cout << "pRes has ResourceObj\n";
    }
    else{
        std::cout << "pRes has no object\n";
    }
}

▼출력

Resource Created
pRes has ResourceObj
Resource Destory

C++ 14 이전에서는, func 함수 내의 pObj의 소유권을, 이 함수가 반환한 임시 객체에 이전하고, 이 임시 객체는 이 소유권을 다시 pRes 객체에 이전합니다.

만약, C++ 17 이후의 버전을 사용하면, 컴파일러는 복사 생략( copy elision )을 수행하기 때문에, pObj 객체로부터 바로 pRes 객체에 소유권을 이동합니다.

 

[C++] 불필요한 중간 단계를 건너뛰기 위한 복사 생략( copy elision )

불필요한 생성자의 호출클래스의 객체를 생성하고 초기화하기 위해, 컴파일러는 이 클래스의 생성자를 호출합니다. 다음은 CSomething 클래스의 객체가 생성될 때, 어떤 생성자가 호출되는지를

codingbonfire.tistory.com

하지만, 어떤 과정을 거치든, 관리되던 ResourceObj 객체는 한 번만 생성되고, 파괴됩니다.

 

 

std::make_unique 함수

make_unique 함수는 C++14에 도입된, unique_prt 객체를 사용하기 쉽도록 만들어 주는 함수입니다.

 

이 함수는 unique_ptr이 관리할 객체를 생성할 뿐만 아니라, 객체를 구성하기 위한 데이터를 전달받아, 객체를 초기화할 수도 있습니다.

make_unique 사용법을 설명하기 위해서, 위에서 사용한 ResourceObj 클래스를 조금 수정해서, 이 클래스의 객체가 가지고 있는 데이터의 크기와 값을 설정할 수 있도록 만들었습니다.

#define SIZE    100

class ResourceObj{
    int* m_pData;
    size_t m_len;
public:
    ResourceObj(){
        m_pData = new int[SIZE];
        m_len = SIZE;
        cout << "Resource Created\n";
    };
    ResourceObj(size_t len, int val){   // ResourceObj의 데이터 크기와 값을 설정
        if ( len == 0) len = SIZE;
        m_len = len;
        m_pData = new int[m_len];
        for( size_t i = 0; i < m_len; i++)
            m_pData[i] = val;
        cout << "Resource Created\n";
    };
    ~ResourceObj(){
        delete [] m_pData;
        cout << "Resource Destory\n";
    };

    bool SetData(int idx, int val){
        if ( m_pData == nullptr || idx >= m_len ) return false;
        m_pData[idx] = val;
        return false;
    }

    bool GetData(int idx, int& val){    // 데이터의 값을 반환
        if ( m_pData == nullptr || idx >= m_len ) return false;
        val = m_pData[idx];
        return true;
    }
};

위의 클래스 객체를 관리하는 unique_ptr 객체는 다음과 같이 생성할 수 있습니다.

int main(){
        
    //unique_ptr<ResourceObj> pRes { new ResourceObj(50, 17) }; 대신
    
    // make_unique 사용 가능
    unique_ptr<ResourceObj> pRes = make_unique<ResourceObj>(50, 17);
    
    int val = 0;
    if (pRes->GetData( 3, val))
        cout << "GetData: " << val << endl;
}

 

make_unique 함수를 사용하면, unique_ptr 객체를 직접 선언하거나, 관리할 객체를 직접 할당하지 않기 때문에, 다음과 같은 실수를 막을 수 있습니다.

int main(){
    
    ResourceObj* pData = new ResourceObj(50, 17);
    unique_ptr<ResourceObj> pRes1(pData);
    unique_ptr<ResourceObj> pRes2(pData);   // 컴파일러에서 오류를 잡아내지 못함
    
    // Do Something
    // ...

    delete pData;	// 할당된 데이터를 직접 삭제. 
}

위에서, pRes1pRes2는 하나의 pData 객체를 동시에 관리하고 있는데, 이것은 관리하고 있는 객체에 대해 독점적인 소요권을 갖도록 만든 unique_ptr 클래스의 의도에 완전히 어긋난 선언입니다.

하지만, 컴파일러는 이 논리적인 실수를 짚어주지 못합니다.

심지어, 위 첫 번째 문장처럼 pData 포인터를 따로, unique_ptr 객체의 선언과 분리해서, 선언함으로써, 무의식적으로 이 포인터를 삭제하는 코드를 작성하는 것도 가능합니다.

 

하지만, make_unique 함수는 함수 내에서 관리할 객체를 생성하고 초기화하기 때문에, 이러한 문제는 발생할 수 없습니다.

 

그리고, make_unique 함수를 사용해도, 배열을 관리하는 unique_ptr을 만들 수 있습니다.

int main(){

    auto ptr = make_unique<ResourceObj[]>(5);	// 크기가 5인 배열 할당

    for( int i = 0; i < 5; i++){	// 배열 객체의 값을 설정
        ptr[i].SetData(i, 20);
    }
}

그렇지만, 이때는 배열의 원소 각각을 초기화할 수는 없기 때문에, 위와 같이 원소의 값을 설정하는 코드를 따로 추가해 주어야 합니다.

 

 

std::vector에 unique_ptr 객체 저장

위에서 설명했듯이, unique_ptr은 복사 생성자( copy constructor )와 복사 대입 연산자( copy assignment operator )를 삭제한 클래스입니다.

그렇기 때문에, 이 클래스의 복사 생성자나 복사 대입 연산자를 호출하는, C++ 표준 라이브러리 컨테이너의 멤버 함수를 사용해서 unique_ptr 객체를 저장할 수 없습니다.
그래서, std::vector와 같은 컨테이너에 unique_ptr 객체를 저장하려면, std::move 함수를 사용하거나, make_unique 함수를 사용하여 이동 연산( move semantics )을 해야 합니다.

int main(){

    vector< unique_ptr<ResourceObj> > vec;
    
    // unique 객체를 rvalue로 저장
    vec.push_back( unique_ptr<ResourceObj>(new ResourceObj(30, 7)) );

    // move 함수를 이용한 rvalue로 저장
    unique_ptr<ResourceObj> ptr{ new ResourceObj(20, 4)};
    vec.push_back( std::move(ptr) );

    // make_unique 함수를 이용한 rvalue로 저장
    vec.push_back( make_unique<ResourceObj>(50, 5) );

    for( const auto& x : vec){	// 벡터에 있는 unique_ptr 순환
        int val;
        if (x->GetData( 3, val)){
            cout << val << " ";
        }
    }
    cout << endl;
}

▼ 출력

Resource Created
Resource Created
Resource Created
7 4 5
Resource Destory
Resource Destory
Resource Destory

std::vector에 unique_ptr 객체를 저장하는데, 위의 세 가지 방법 모두 사용이 가능합니다.

그런데, 두 번째의 ptr 객체는, std::vector에 저장될 unique_ptr 객체에, 관리하고 있던 데이터를 이동했으므로, 더 이상 관리하는 객체가 없다는 것에 주의해야 합니다.

 

참고로, std::vectoremplace 멤버 함수를 사용하여, 내부에서 바로 unique_ptr 객체를 생성할 수도 있습니다.

int main(){

    vector< unique_ptr<ResourceObj> > vec;
    
    // emplace를 이용한 내부 객체 생성
    vec.emplace_back( new ResourceObj(40, 11) );

    for( const auto& x : vec){
        int val;
        if (x->GetData( 3, val)){
            cout << val << " ";
        }
    }
    cout << endl;
}

▼ 출력

Resource Created
11
Resource Destory

emplace 함수는 ResourceObj 객체의 포인터를 전달받아, 함수 내부에서 unique_ptr<ResourceObj> 타입의 객체를 생성하고, vec에 저장합니다.

 

[C++] 내부 삽입을 통해 원소를 추가할 수 있는 std::emplace

std::emplace 함수std::emplace 함수는 C++ 표준 라이브러리의 컨테이너들( vector, list, set, map, deque 등 )에 새로운 원소를 삽입하는 함수입니다. 이와 비슷한 함수로 std::vector의 emplace_back, std::list의 emplace

codingbonfire.tistory.com

그럼으로써, unique_ptr의 소유권이 이전되는 과정을 건너뛸 수 있습니다.

 

 

정리

  • std::unique_ptr은 메모리가 할당된 객체를 저장하고, 자동으로 삭제하는 스마트 포인터입니다.
  • std::unique_ptr은 관리하는 객체에 대한 독점적인 소유권을 갖기 때문에, 이 unique_ptr 객체를 복사할 수 없습니다.
  • make_unique 함수를 사용하면, unique_ptr를 사용하면서 발생할 수 있는 실수를 방지할 수 있습니다.
  • unique_ptr 객체가 관리하는 객체를 다른 unique_ptr에서 관리하려면, 이동 연산( move semantics )을 사용해서 소유권을 이전해야 합니다.

 

 

이 글과 관련 있는 글들

자원을 공유하는 shared_ptr 객체

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유