SmartPointer
흠...
이번 시간엔 꽤나 영특한 녀석을 소개하고자 한다.
바로 '스마트 포인터' 란 놈인데
게으르고 주위 깊지 못한 본인과 같은 프로그래머 들에겐
한 줄기 단비와 같은 존재가 아닐수 없다.
* 첨부된 예제에서 사용된 스마트 포인터는 boost 라이브러리에 속한
Shared Pointer( shared_ptr ) 란 놈이다. 따라서 이놈을 사용하려면 미리 라이브러리를
설치 해야 하는데 이곳에서 알아보도록 ( http://www.boost.org )
동적 할당의 그림자
C++ 과 같은 객체 지향 언어에서 객체를 동적 할당을 이용해 생성하는 것은
아주 자연스러운 일이고 그 만큼 비일 비재하게 발생하게 된다.
그러나 모든 일에는 책임이 따르는 법, 똥을 쌌으면 누군가는 치워야하는 것이다.
즉, 객체에 대해서 동적 할당된 메모리는 사용이 끝났으면 반드시 Heap 영역에 반납해야한다.
그렇지 않고 이것을 그대로 방치해 둔다면 소위 메모리 누수( Memory Leak ) 라는
끔찍한 현상이 발생하고 이것이 반복적으로 누적돼 메모리가 줄줄 센다면
동작중인 프로그램이 메모리 부족 현상으로 갑작시리 비명 횡사( 멈추거나 혹은 죽거나 )할 것이고
만일, 우연히 지나가는 회사 사장이 그 장면을 목격하기라도 하는 날엔... 어휴
물론 이렇게 말할수 있다.
" 훗... 그 까짓거, 뭐 어려운 일도 아니군. 쓴 다음에 꼬박 꼬박 반납하면 되지 않은가? "
당연히 옳은 소리다.
그런데 누구는 자신의 무능함을 뽐내기 위해 프로그램에 일부러 버그를 심어 놓겠나?
사람은 누구나 실수를 저지른다. 그 때문에 사람의 실수를 줄여가기 위한 제약이나 안전 장치
가 추가돼는 방향으로 프로그래밍 체계는 발전하고 있는것이며
객체 지향 방법론 또한 그런 연유로 기존 프로그래밍 방식을 제치고 대세가 된 것이다.
더군다나 지금 소개하게 될 뛰어난 안전장치가 있다면 구지 그것을 팽개치고
위험한 길( 버그가 발생할 확률이 높은 )을 택할 이유는 없지 않은가?
메모리 누수( Memory Leak )
쉽게 말해 힙으로 부터 메모리를 예약( 동적 할당 ) 한 후에
사용이 끝났음에도 그것을 다시 힙에게 돌려주지 않고, 끝내는 돌려주고 싶어도
돌려줄수 없는 상황이 올때 보통 흔히들 " 메모리가 세는군. 젠장!!! "이라고 말한다.
뭐 더 이상의 말은 의미가 없으니 바로 예제 들어간다.
//---------------------
// class CEnemy
//---------------------
class CEnemy{ };
...
//-------------------------------
// Client Code
//-------------------------------
void Func()
{
...
CEnemy* pEnemy = new CEnemy( ... ); // CEnemy 객체를 생성( 동적할당 )한다
...
/* 여기서 생성된 CEnemy 객체를 지지고 볶는다 */
...
delete pEnemy; // 다 썼으면 반드시 동적할당 받은 메모리를 삭제( Heap 에게 돌려줌 ) 한다
}
이것이 기본 적인 원칙이다.
반드시 new 연산자를 통해 동적 할당을 이용했다면 delete 연산자로 동적 할당을 해제해야한다.
물론 위의 예제에서 이 원칙을 지키는 것은 아주 간단하게 보인다.
그러나 객체를 생성한 시점 부터 삭제 되기 전까지, 즉 객체를 실제로 활용하는 부분에서
Func() 함수에서 리턴하게 돼는 부분이 존재 한다면 어떻게 될것인가?
void Func()
{
...
CEnemy* pEnemy = new CEnemy( ... ); // CEnemy 객체를 생성( 동적할당 )한다
...
if( Condition == ERROR )
{
return; // 이렇게 함수를 빠져 나갈 경우, 메모리 누수가 발생
}
...
delete pEnemy; // 다 썼으면 반드시 동적할당 받은 메모리를 삭제( Heap 에게 돌려줌 ) 한다
}
즉, 메모리를 해제 하기 전에 빠져 나갈 여지가 자의든 타의든 존재하게됀다는 것이다.
뭐 해당 함수를 빠져 나가는 모든 경우에 객체 소멸 코드를 넣을수도 있지만
아무래도 이런 부분이 많아지다 보면 실수하고 빼먹을 확률이 높아지는 것이 사실이다.
객체를 생성한 뒤에 구지 delete 연산을 명시적으로 호출하지 않더라도
해당 함수 영역을 빠져 나갈때 알아서 객체가 소멸돼는 기능이 있으면 위에서와 같은
쓰잘데기 없는 고민으로 짧지도 않은 인생을 낭비할 필요가 없을것이다.
이럴때 써먹으라고 Smart Pointer 가 있는 것이다!!!
스마트 포인터( Smart Pointer )
스마트 포인터는 어떤 동적할당 된 객체를 가르키고 있다가
-보통 스마트 포인터를 만들시 가르킬 객체의 포인터로 초기화 한다.
만일 자신이 속한 블럭(혹은 함수 )에서 빠져 나갈시 자동으로 자신이 물고 있는
객체 포인터에 대해 자동으로 소멸자( delete )를 호출시킨다.
그러니까 쉽게 말해 일단 사용할 객체를 동적 할당으로 생성한 뒤에 이것을
스마트 포인터가 가르키가 한다면 그 이후로 객체 소멸에 대해 전혀 신경쓸 거리가
없어진다는 얘기다.
void Func()
{
...
SmartPointer< CEnemy > sp( new CEnemy( ... ) ); // 이제 부터 구질 구질한 일들과 안녕이다.
...
}
기본적인 원리는 상당히 간단하다.
위에서 보다시피 스마트 포인터는 자동 지역 객체( Auto Local Instance )이고 이놈들의 특징은
자신이 속한 함수( 혹은 블럭 )이 호출될시 스택에서 생성되며
자신이 속한 블럭 범위에서 벗어날시 자동으로 스택에서 삭제 된다는 것이다.
그런 자동 지역 객체( 변수 )의 성질 때문에 스마트 포인터의 소멸자에서
가르키고 있던 객체가 값이 있다면 그것에 대해 delete 연산을 한번 호출해주면 땡이다.
//----------------------------------------------------------------
// class CSmartPointer
//----------------------------------------------------------------
// 한번 만들고 싶어 대충 휘갈긴 스마트 포인터
//----------------------------------------------------------------
#ifndef _C_SMART_POINTER_H_
#define _C_SMART_POINTER_H_
template< typename T >
class CSmartPointer
{
//--------------------------------------
// 공용 인터페이스
//--------------------------------------
public:
//-------------------
// 연산자 오버로딩
//-------------------
operator T*(){ return m_Pointer; } // 암시적 타입 변환 연산자
T* operator->(){ return m_Pointer; } // 역 참조 연산자
//--------------------------------------
// 생성자 및 소멸자
//--------------------------------------
public:
explicit CSmartPointer( T* pInstance )
{
if( pInstance )
{ m_Pointer = pInstance; }
}
~CSmartPointer()
{
if( m_Pointer )
{ delete m_Pointer; }
}
//--------------------------------------
// 멤버 변수
//--------------------------------------
private:
T* m_Pointer;
}; // class CSmartPointer
#endif //_C_SMART_POINTER_H_
쩝... 물론 이정도로 단순하진 않겠지만 아마 기본원리는 이럴 것이다.
'C & C++ 관련' 카테고리의 다른 글
auto_ptr 템플릿 클래스 소개 (0) | 2009.07.26 |
---|---|
string 객체의 c_str() 함수 (0) | 2009.06.25 |
c++ 반복자 (0) | 2009.06.25 |