아래의 예제를 봅시다.
1 2 3 4 5 6 | void function() { Inverstment * pInv = createInverstment(); //팩토리함수 ... delete pInv; //객체 삭제 } |
createInvestment() 함수를 통해 Investment 클래스의 포인터를 가져와서, pInv의 어떠한 동작을 수행한뒤, pInv의 메모리를 해제 합니다.
정상적인 수행을 하게되면, 당연히도 delete pInv; 까지 내려가서 수행후 함수호출을 빠져나오는것을 생각하지만, 여기에선 return 문을 만나 바로 함수호출을 빠져 나간다든지, 많은 예외가 있습니다. 즉 ! 메모리 누수가 발생하게 됩니다.
우리는 createInvestment() 함수로 얻어낸 자원이 항상 해제되도록 만들어야 합니다. 그러기위해서는 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하며, 그 소멸자는 실행 제어가 function()함수를 떠날 때 호출되도록 만드는 것입니다.
표준라이브러리를 보면 auto_ptr이 있는데 위에있는 용도에 쓰라고 마련된 클래스입니다.
std::auto_ptr< 생성할 클래스 > 변수명( 동적할당된 포인터 )
이렇게 자원을 획득(동적할당)한후 바로 자원 관리 객체에게 넘겨주는데, 자원 획득 하자마자 초기화 하는 이러한 방법을 RAII(Resource Acquisition is Initialization) 이라고 합니다.
std::auto_ptr 은 자신이 소멸될 때 자신이 가리키고 있는 대상에 대해 자동으로 delete를 수행합니다.
그렇기 때문에 어떤 객체를 가리키는 auto_ptr의 개수가 둘 이상이면 절대로 안됩니다. 만약에 이런사태가 벌어지면 결국 자원이 두번 삭제 될테니 큰 문제가 됩니다.
위와같은 문제 때문에 std::auto_ptr은 객체를 복사하면, 원본 객체는 NULL로 만듭니다. 복사하는 객체만이 그 자원의 유일한 소유권을 갖는다고 가정합니다.
1 2 3 4 5 6 7 8 9 | void function(){ std::auto_ptr<A> pB1(new A()); //복사되는 순간 pB1은 NULL, pB2가 A객체를 가르킴 std::auto_ptr<A> pB2(pB1); // 역시 대입되는 순간 pB2는 NULL, pB1이 A객체를 가르킴 pB1 = pB2; } |
여러번 할당해제되는 문제를 막을 수 있지만, 정상적인 복사 동작을 요구하는 STL컨테이너에서는 auto_ptr 객체를 원소로서 허용하지 않습니다.
그래서 그 대안으로 참조 카운팅 방식 스마트 포인터 (reference-counting smart pointer: RCSP)가 있다. RCSP는 특정한 어떤 자원을 가리키는 외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원을 자동으로 삭제하는 스마트 포인터 입니다.
1 2 3 4 5 6 7 | void f(){ A * pA = create(); //팩토리 함수, 객체 생성 std::tr1::shared_ptr<A> pB1(pA); //최초 생성시 참조 카운트 1 std::tr1::shared_ptr<A> pB2(pB1); //복사 -> 카운트 2 std::tr1::shared_ptr<A> pB3 = pB1;//대입 -> 카운트 3 } |
어떤 함수내에서 shared_ptr을 이용해서 객체를 사용하고 나면 여러개의 shared_ptr이 같은 객체를 참조 하고 있다하더라도 함수가 끝나면서 지역변수였던 모든 shared_ptr을 해제 하기 때문에 자연스럽게 동적할당된 자원이 반환되게 됩니다.
이렇듯, 스마트 포인터는 아주 중요한 부분이며, 자원관리에 효율적입니다.
하지만 이러한 스마트 포인터의 소멸자에 있는 delete 연산자는 delete [] 연산자가 아닙니다.
즉!
스마트 포인터 인자로 배열을 받으면 안된다는 것입니다.
std::auto_ptr<int> num( new int[10] ); // 문제가 발생합니다. 배열을 쓰면 안됩니다.
배열에 쓸수 있는 auto_ptr이라든지 tr1::shared_ptr을 원한다면, Boost라이브러리에 있는
boost::scoped_array와 boost::shared_array를 알아 보면 됩니다.
이것 만은 잊지 말자!
◆ 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII 객체를 사용합시다.
◆ 일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptr 그리고 auto_ptr 입니다.
이 둘 가운데 tr1::shared_ptr이 복사 시의 동작이 직관적이기 때문에 대개 더 좋습니다. 반면, auto_ptr은 복사되는 객체(원본 객체)를 NULL로 만들어 버립니다.