프로그래밍/C/C++2009. 9. 17. 10:32
C++에서는 new연산자를 통해 객체를 생성한 경우 memory leak을 막기 위해 객체의 사용이 끝난 후 반드시 delete연산자를 호출해야 합니다. 이론은 쉽지만 프로그래밍의 세계에서는 수많은 변수와 예외로 인해 이러한 기본적인 원칙을 지키기가 참 어렵습니다.

이처럼 골치아픈 C++ 메모리 관리에 도움을 줄 수 있는 std::auto_ptr 템플릿에 대해 정리해보겠습니다.

1. auto_ptr ?
auto_ptr은 C++ 표준 템플릿 라이브러리(Standard Template Library, STL)에서 찾을수 있습니다. STL은 C++에서 사용할 수 있는 컨테이너 클래스와 알고리즘, 자료구조를 일반화시켜 제공해주는 라이브러리 입니다. STL은 제네릭 라이브러리라고도 불리며, 이러한 일반화를 구현하기 위해 C++에서 제공하는 Template를 사용합니다. auto_ptr은 바로 이 STL의 구성요소중에 하나로서 스마트 포인터의 여러종류 중에 하나입니다.

* STL에는 auto_ptr이 유일한 스마트 포인터 입니다. boost 등의 라이브러리는 다양한 종류의 스마트 포인터를 제공합니다.


2. 스마트 포인터란?
스마트 포인터란 C++에서 제공하는 기본 포인터 자료형을 흉내내면서 거기에 더불어 부가적인 기능을 가지고 있는 C++ 객체로, 주로 operator->, operator* 연산자를 오버로딩하는 방법을 통해 구현됩니다. 주요 특징으로 포인터에 대한 소유권 관리 기능이 있는데, 소유권을 관리하는 방법은 꽤 다양하며 이에따라 스마트 포인터의 종류도 여러가지가 있습니다.

auto_ptr의 경우 소유권을 자동이전해주는 스마트 포인터로서, 포인터를 복사할 경우 원래의 스마트 포인터는 null을 지정하게 되고, 복사를 받은 스마트 포인터가 해당 포인터에 대한 소유권을 가지게 됩니다.


3. 사용예
class Widget;
void other_func();	// maybe, throw exception
int some_func() {
	Widget *w = new Widget;
	if (...) {
		delete w;
		return -1;
	}
	other_function();
	delete w;
	return 0;
}
위와 코드를 살펴보겠습니다. 
Widget 객체에 대한 포인터는 new 연산자를 통해 할당되었으므로, 반드시 delete를 호출해주어야 memory leak이 발생하지 않습니다. 하지만 위의 코드에서는 Widget 객체 포인터가 delete되지 않을 가능성이 있습니다.

바로 9라인의 other_function() 메소드가 예외를 던지게 된다면 10라인의 delete 코드는 수행되지 않으며, some_func() 메소드가 리턴될 것입니다. 이는 명백하게 memory leak을 발생시키는 원인이 됩니다.

이러한 문제를 방지하기 위해서는 다음과 같이 other_func() 메소드에서 던질수 있는 예외를 처리하기 위해 try-catch 구문을 추가합니다.
class Widget;
void other_func();	// maybe, throw exception
int some_func() {
	Widget *w = new Widget;
	if (...) {
		delete w;
		return -1;
	}
	try {
		other_function();
	} catch (other_function_exception& e) {
		delete w;
		throw;
	}
	delete w;
	return 0;
}
하지만 위의 코드는 무언가 깔끔하지 못한 느낌을 주며, 예외를 던질 수 있는 메소드에 대해 무조건 try-catch 구문을 남발하는 것도 좋은 방법은 아닙니다. 

바로 이러한 문제에 대한 정확하고 깔끔한 해결책을 제시해줄 수 있는 것이 auto_ptr입니다.
#include <memory>
class Widget;
void other_func();	// maybe, throw exception
int some_func() {
	std::auto_ptr<Widget> pw(new Widget);
	if (...) {
		return -1;
	}
	other_function();
	return 0;
}
어떻게 위의 코드는 자동으로 메모리를 반환하는 것일까요? 
바로 local에 생성된 객체는 stack unwinding시에 항상 파괴가 보장된다는 점을 이용한 것입니다. 따라서 return이나 throw의 stack unwinding시에 local 객체인 pw의 소멸자가 호출되게 되고 이때 auto_ptr는 자신이 가지고 있는 pointer를 delete하게 됩니다.

auto_ptr은 다음과 같은 특징을 지니고 있습니다.

  • auto_ptr의 소멸자는 자신이 가지고 있는 pointer를 delete합니다.
  • release() 함수는 자신이 가지고 있는 pointer의 제어를 다시 일반 포인터로 넘겨줍니다.
  • reset(X* p = 0) 함수는 현재 자신이 가지고 있는 pointer를 delete한 후 새로운 pointer의 제어를 가집니다.
  • get() 함수는 자신이 가지고 있는 pointer의 값을 알려줍니다.
  • auto_ptr가 복사될때는 원본이 되는 auto_ptr는 더이상 pointer의 소유권를 가지지 않게 됩니다. (대신 원본 auto_ptr는 null pointer를 가지게 됩니다.)

코딩시 자신도 모르게 auto_ptr의 소유권이 바뀌는 일을 방지하고자 할 경우에는 const auto_ptr idiom을 사용하시면 됩니다. 다음 코드를 참고하세요.
const auto_ptr<T> pt1(new T);

auto_ptr<T> pt2(pt1);	// error
auto_ptr<T> pt3;
pt3 = pt1;		// error
pt1.release();		// error
pt1.reset(new T);	// error
마지막으로 표준에 정의된 auto_ptr은 다음과 같습니다.
namespace std {
	template<class X> class auto_ptr {
		template <class Y> struct auto_ptr_ref {};
	public:
		typedef X element_type;
		// construct/copy/destroy:
		explicit auto_ptr(X* p =0) throw();
		auto_ptr(auto_ptr&) throw();
		template<class Y> auto_ptr(auto_ptr<Y>&) throw();
		auto_ptr& operator=(auto_ptr&) throw();
		template<class Y> auto_ptr& operator=(auto_ptr<Y>&) throw();
		~auto_ptr() throw();
		// members:
		X& operator*() const throw();
		X* operator->() const throw();
		X* get() const throw();
		X* release() throw();
		void reset(X* p =0) throw();
		// conversions:
		auto_ptr(auto_ptr_ref) throw();
		template<class Y> operator auto_ptr_ref<Y>() throw();
		template<class Y> operator auto_ptr<Y>() throw();
	};
}
Posted by devop

댓글을 달아 주세요