C++ unique_ptr 스마트 포인터의 개념
C++ 표준에서는 unique_ptr, shared_ptr, weak_ptr의 3종류의 스마트 포인터를 제공하는데, 이번 포스팅에서는 unique_ptr 스마트 포인터의 특징과 사용법에 대해 설명한다.
unique_ptr 포인터는 스마트 포인터의 일종으로 "적절한 타이밍에 자동으로 힙 메모리 공간을 해제"하는 기능도 있다. shared_ptr 포인터와의 가장 큰 차이점은 unique_ptr 포인터가 가리키는 힙 메모리를 다른 unique_ptr과 공유할 수 없다는 것이다.
이것은 각 unique_ptr 포인터가 가리키는 힙 메모리 공간의 참조 횟수가 1일 수 있음을 의미한다. unique_ptr 포인터가 가리키는 힙 메모리 공간의 소유권을 포기하면 공간이 즉시 해제되고 회수된다.
unique_ptr 는 제네릭 템플릿 클래스의 형태로 제공되며, unique_ptr<T>(T는 포인터가 가리키는 데이터 유형)는 <memory> 헤더 파일에 정의되고 std 네임스페이스에 위치한다. 따라서 unique_ptr 를 사용하려면 먼저 다음 두 명령문이 있어야 한다.
#include <memory>
using namespace std; // 이 문장은 필수는 아니다. 생략할 경우 unique_ptr 앞에 std:: 가 필요하다.
unique_ptr 생성
다양한 실제 시나리오의 요구 사항을 고려하여 unique_ptr<T> 템플릿 클래스는 여러 실용적인 생성자를 제공한다. 다음은 unique_ptr 스마트 포인터를 구성하는 몇 가지 일반적인 방법이다.
- unique_ptr 는 다음과 같이 생성이 가능하다.
// C Style
Test* pTest = new Test;
delete pTest;
// unique_ptr Style ( C++14 부터 )
auto pTest = make_unique<Test>();
// unique_ptr Style ( C++14 이전 )
unique_ptr<Test> pTest(new Test);
여기서 주목할 사항은 delete가 자동 호출되기 때문에 따로 적을 필요가 없다는 것이다.
unique_ptr 장점
다음과 같이 Car 객체를 힙에 할당한 뒤 이를 해제하지 않아 스코프를 빠져나가 메모리 누수 현상이 발생하는 경우를 살펴보자.
void leak1()
{
Car* myCar = new Car();
myCar->go();
// 메모리를 해제하지 않았다.
}
코드를 작성할 때마다 항상 동적 할당한 메모리를 제대로 해제할거라고 생각할 수 있지만 그렇지 않을 가능성이 훨씬 높다.
void leak2()
{
Car* myCar = new Car();
myCar->go();
delete myCar;
}
위 함수는 Car 객체를 동적 할당하여 사용 후 delete를 호출한다. 하지만 이렇게 해도 메모리 누수가 발생할 가능성은 남아 있다. go() 메서드에서 exception이 발생하게 되면 delete가 실행되지 않기 때문이다.
때문에 이 코드들 모두 unique_ptr로 구현해야 한다. 그러면 unique_ptr 인스턴스가 스코프를 벗어나면 (함수가 끝나거나 exception이 발생하는 경우) 소멸자가 호출될 때 객체가 자동으로 해제된다.
void noLeak()
{
auto myCar = make_unique<Car>();
myCar->go();
}
unique_ptr 사용법
스마트 포인터는 일반 포인터와 똑같이 * 나 -> 로 역참조한다.
myCar->go();
(*myCar).go();
다음의 코드에 나오는 예제는 unique_ptr의 유용한 메서드들이다.
/*
* get( ) 예제
*/
void doSomething(Car* car){ /* 스마트 포인터를 사용하는 내용 */ };
auto myCar = make_unique<Car>();
doSomething(myCar.get());
// get( ) 메서드를 이용하면 내부 포인터에 직접 접근 가능하다.
// 일반 포인터만 전달 가능한 함수에 스마트 포인터를 전달할 때 유용하다.
/*
* reset( ) 예제
*/
myCar.reset(); // 리소스 해제하고 nullptr로 초기화
myCar.reset(new Car()); // 리소스 해제하고 새로운 인스턴스로 설정
/*
* release( ) 예제
*/
Car* newCar = myCar.release(); // 소유권을 해제한다.
// newCar를 사용하는 코드
delete newCar;
newCar = nullptr;
// release( )를 이용하면 unique_ptr와 내부 포인터의 관계를 끊을 수 있으며,
// 리소스에 대한 내부 포인터를 리턴한 뒤 스마트 포인터를 nullptr로 설정한다.
// 그러면 스마트 포인터는 해당 리소스에 대한 소유권을 잃게 되며, 리소스는 반드시 직접 해제해야 한다.
/*
* move( ) 예제
*/
class Foo
{
public:
Foo(unique_ptr<int> data) : mData(move(data)) { }
private:
unique_ptr<int> mData;
};
auto myInt = make_unique<int>(42);
Foo f(move(myInt));
// unique_ptr는 단독 소유권을 표현하기 때문에 복사가 불가능하다.
// 대신 위처럼 소유권의 명시적 이동이 가능하다.
커스텀 제거
기본적으로 unique_ptr는 new와 delete로 메모리를 할당하거나 해제하지만 다음과 같이 변경할 수도 있다.
int* malloc_int(int val)
{
int* p = (int*)malloc(sizeof(int));
*p = value;
return p;
}
int main()
{
unique_ptr<int, decltype(free)*> myInt(malloc_int(7), free);
return 0;
}
'C | C++' 카테고리의 다른 글
C++ Vector reserve( )와 resize( )의 차이 (0) | 2022.09.29 |
---|---|
auto 키워드 (1) | 2022.09.10 |
Rvalue reference 정리한 내용 (0) | 2020.11.20 |
const 와 readonly 의 차이 (0) | 2020.05.22 |
c++ 구조2 (0) | 2019.10.03 |