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 객체를 초기화하기 위해 release와 reset 멤버 함수를 호출할 수 있습니다.
먼저 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; // 할당된 데이터를 직접 삭제.
}
위에서, pRes1
과 pRes2
는 하나의 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::vector
의 emplace 멤버 함수를 사용하여, 내부에서 바로 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 )을 사용해서 소유권을 이전해야 합니다.
이 글과 관련 있는 글들
'C, C++ > 표준 라이브러리' 카테고리의 다른 글
[C++] shared_ptr 클래스를 지원하기 위한 std::weak_ptr (0) | 2025.04.10 |
---|---|
[C++] 자원을 공유하는 std::shared_ptr 객체 (0) | 2025.04.09 |
[C++] 내부 삽입을 통해 원소를 추가할 수 있는 std::emplace (0) | 2025.03.27 |
[C++] 여러 개의 값을 저장할 수 있는 std::tuple (0) | 2025.03.26 |
[C++] 초기값 리스트를 이용한 객체 초기화와 std::initializer_list (0) | 2025.03.24 |