자원 관리에는 객체가 그만!


class Investment { ... }  --> Base 클래스


Investment* createInvestment();

--> Drived 클래스들의 객체를 동적 할당하고

      그 포인터를 반환하는 팩토리 함수.


이 객체의 해제는 호출자 쪽에서 직접 해야 한다.


void f(){

investment *pInv = createInvestment();

....

....

delete pInv;

}


언뜻 보기엔 별 문제 없어 보인다.

1. 하지만 ... 부분에서 return; 이 있을 경우

2. continue, goto가 있을 경우

3. 예외가 발생될 경우

위의 경우 delete는 실행 되지 않고

메모리가 누출되고 자원이 새게 됩니다.




해결책

createInvestment() 함수로 만들어낸 자원이

항상 해제되도록 만드는 방법은

자원을 객체에 넣고 그 자원 해제를

소멸자가 맡도록 하며, 그 소멸자는 실행 제어가

f를 떠날 때 호출 되도록 만드는 것이다.




개발에 쓰이는 상당수의 자원이 힙에서 동적으로

할당되고, 하나의 블록 혹은 함수 안에서만 쓰이는

경우가 잦기 때문에 그 블록 혹은 함수로부터

실행 제어가 빠져 나올 때 자원이 해제되는 것이 맞다.




스마트 포인터 auto_ptr을 써보자.

가리키고 있는 대상에 대해 소멸자가

자동으로 delete를 불러주도록 설계되어 있다.


void f(){

auto_ptr<Investment> pInv (createInvestment());

...

...

}

--> 블럭이 끝나면 auto_ptr의 소멸자를 통해 pInv를 삭제한다.


위와 같이 자원 관리 객체를 사용하는 

방법의 중요한 특징에 대해 알아보자.




1. 자원을 획득한 후에 자원 관리 객체에 넘긴다.

createInvestment() 함수가 만들어준 자원을

auto_ptr 객체를 초기화 하는 데 쓰이고 있다.

--> 자원 획득 초기화 (RAII)

자원 획득과 자원 관리 객체의 초기화가 

한 문장에서 이루어지는 것이 일반적이다.


2. 자원 관리 객체는 자신의 소멸자를 사용해

자원이 확실히 해제되도록 해야한다.

소멸자는 객체가 소멸될 때 자동적으로

호출되기 때문에, 실행 제어가 어떤 경위로

블록을 떠나가는가에 상관없이 자원 해제가

제대로 이루어지게 되는 것이다.




주의점!!

auto_ptr은 자신이 소멸될 때, 자신이 가리키는

객체를 자동으로 delete를 해주게 된다.

따라서 같은 객체를 두개의 auto_ptr이

가리키고 있다면 자원이 두번 삭제되는 결과.


---> 때문에 auto_ptr은 객체를 복사하게 되면

원본객체는 null로 만들어버린다.

예제를 보도록 하자.


auto_ptr<Investment> pInv1(createInvestment());

auto_ptr<Investment> pInv2(pInv1);

--> pInv2가 객체를 가리켜 pInv1은 null

pInv1 = pInv2;

--> 이제는 pInv2가 null


문제점 : STL 컨테이너의 경우엔 원소들의 정상적인 복사 동작을

가져야 하기 때문에 auto_ptr은 최선의 방법이 아니다.


해결책

참조 카운팅 방식 스마트 포인터 (RCSP)를 쓰자!

참조 카운팅은 여러개의 객체(자원 관리 객체)들이 동일한 값을 가질 때,

그 객체들로 하여금 그 값을 나타내는 하나의 데이터를

공유하게 하여 데이터의 양을 절약하는 기법이다.


어떠한 자원을 참조하는 횟수를 기록하여 자원의 생명주기를 관리한다.

이 횟수가 있다는 것은 메모리 어딘가에서 쓰이고 있다는 것이다.

함부로 자원을 삭제하면 사용 중인 정보가 무효화 될 것이다.

따라서 참조 횟수가 0이 되기 전에는 이 자원이 삭제되지 않는다.


TR!에서 제공하는 std::tr1::shared_ptr이 대표적인 RCSP이다.


void f(){

shared_ptr<Investment> pInv1 (createInvestment());

shared_ptr<INvestment> pInv2(pInv);

pInv1 = pInv2;

}


--> 복사와 대입 문제없이 실행된다.


주의점!

스마트 포인터의 소멸자에서는 delete 연산자를

사용하고 있다는 것이다. delete[]가 아니라는 점!

따라서 동적 할당된 배열에 대해 스마트 포인터를

사용하지 말자는 것. 동적 할당된 배열은 vector이나

string을 통해 거의 대체할 수 있기 때문이다.



POINT!!

1. 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고

소멸자에서 그것을 해제하는 RAII객체를 사용하자.


2. 널리 쓰이는 RAII는 대표적으로 auto_ptr, shared_ptr이 있다.


+ Recent posts