본문 바로가기

Programming/CPP11&14

[C++11] 새로운 스마트 포인터 unique_ptr(auto_ptr의 대체)

반응형

C++11에서 auto_ptr이 사라지고 unique_ptr이 새로 추가되었습니다.

auto_ptr의 문제점에 대해서는 아래의 글을 확인하시면 됩니다.

2014/12/08 - [Programming/C&C++] - 스마트하지 못한 스마트한 포인터 auto_ptr

auto_ptr을 대체하는 unique_ptr에 대해서 알아보도록 하겠습니다.

unique_ptr은 auto_ptr과 거의 유사한 멤버를 가지고 있습니다.

동적 할당된 포인터를 받아서 해당 포인터를 핸들링하고 자동으로 메모리를 해제하는 역할까지 동일합니다.

* 연산자나 -> 연산자도 auto_ptr과 동일하게 지원합니다.

unique_ptr을 생성하는 코드를 보도록 하겠습니다.

#include <string>
#include <memory>
#include <iostream>

struct TestStuff
{
	TestStuff() : nIntVal(0), szVal(){};
	TestStuff(int nValue, std::string szValue) : nIntVal(nValue), szVal(szValue){};

	int nIntVal;
	std::string szVal;
};

int main()
{

	// Make unique_ptr variable
	std::unique_ptr<TestStuff> ptr_Temp1(new TestStuff{ 3, "Hello" });
	std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;

	// Make unique_ptr variable using std::make_unique
	std::unique_ptr<TestStuff> ptr_Temp2 = std::make_unique<TestStuff>(4, "Hi");
	std::cout << (*ptr_Temp2).nIntVal << " and " << (*ptr_Temp2).szVal << std::endl;

	return 0;
}

std::unique_ptr<T> 의 형식으로 변수를 선언하고 new로 메모리를 할당해서 생성이 가능합니다.

또한 C++ 14에서 새로 추가된 std::make_unique()를 사용해서 생성도 가능합니다.

물론 C++ 14를 지원하는 컴파일러로 작업을 해야 합니다.

ptr_Temp1과 ptr_Temp2가 생성이 됩니다.

unique_ptr을 사용하는 방법은 다음과 같습니다.

int main()
{
	// Make unique_ptr variable
	std::unique_ptr<TestStuff> ptr_Temp1(new TestStuff{ 3, "Hello" });
	std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;

	// Make unique_ptr variable using std::make_unique
	std::unique_ptr<TestStuff> ptr_Temp2 = std::make_unique<TestStuff>(4, "Hi");
	std::cout << (*ptr_Temp2).nIntVal << " and " << (*ptr_Temp2).szVal << std::endl;

	ptr_Temp1.reset(); // ptr_Temp1 is nullptr.
	if (ptr_Temp1) // operator bool
		std::cout << ptr_Temp1->nIntVal << " and " << ptr_Temp1->szVal << std::endl;
	else
		std::cout << "Empty" << std::endl;

	ptr_Temp1.reset(new TestStuff{ 5, "Bye" }); // ptr_Temp1 is assigned.

	TestStuff* pStuff = ptr_Temp2.release(); // ptr_Temp2 is nullptr.
	if (ptr_Temp2)
		std::cout << ptr_Temp2->nIntVal << " and " << ptr_Temp2->szVal << std::endl;
	else
		std::cout << "Empty" << std::endl;

	delete pStuff; // must delete assigned memory.

	// Must use std::move() for move pointer.
	ptr_Temp2 = std::move(ptr_Temp1); // ptr_Temp1 is nullptr, ptr_Temp2 is 5, "Bye"

	if (ptr_Temp2)
		std::cout << ptr_Temp2->nIntVal << " and " << ptr_Temp2->szVal << std::endl;
	else
		std::cout << "Empty" << std::endl;

	return 0;
}

reset()은 내부에 있는 포인터의 메모리를 해제하고 nullptr로 변경합니다.

reset()을 사용할 때 reset(new TestStuff{...}) 등으로 사용하면 새로운 메모리 할당이 가능합니다.

또한 unique_ptr은 그 자체로 bool연산이 가능해서 nullptr일 경우 false를 리턴합니다.

또한 release() 멤버 함수를 사용해서 unique_ptr 내부의 포인터를 다른 포인터로 옮기는 것도 가능합니다.

이 때는 반드시 받은 포인터의 메모리를 해제해 줘야 합니다.

또한 = 연산자로 auto_ptr과 동일하게 포인터를 옮길 수 있지만 std::move()를 사용해서 옮겨야 합니다.

아래와 같은 방법을 사용하면 동적 할당된 배열에 대해서도 처리가 가능합니다.

std::unique_ptr<int[]> ptr(new int[4]{1, 2, 3, 4});

배열에 대한 deleter가 구현이 되어 있기 때문에 int[] 등으로 사용하게 되면 내부적으로는 delete[]가 호출이 됩니다.

마지막으로 unique_ptr은 아래와 같이 deleter를 직접 구현하는 것도 가능합니다.

struct ArrayDeleter
{
	template <class T>
	void operator()(T* p)
	{
		delete[] p;
	}
};

int main()
{
	std::unique_ptr<int[], ArrayDeleter> ptr(new int[4]{1, 2, 3, 4});
}

STL의 functor를 활용해서 delete를 직접 구현하고 변수 선언할 때 넣어줄 수 있습니다.

이렇게 하면 메모리를 동적 할당하는 방법에 제약이 생기지 않습니다.

또한, functor를 활용하면 내부에서 추가적으로 할 동작을 정의할 수 있는 장점이 있습니다.

메모리 누수의 걱정이 생길 때는 unique_ptr를 활용해서 개발을 하시면 큰 도움이 될 것입니다.

unique_ptr의 설명을 마치겠습니다.

반응형