투자를 모델링해주는 클래스 라이브러리를 가지고 어떤 작업을 한다고 가정하자.

 

1
2
3
4
5
6
class Investment
{
    ...
};
 
Investment* createInvestment(); //팩토리 함수.
cs

 

createInvestment 함수를 통해 객체를 얻기 때문에 사용자가 직접 객체를 해제시켜줘야 한다.

 

1
2
3
4
5
6
7
8
9
void func()
{
    Investment *pInv = createInvestment(); //팩토리 함수를 통해 객체 생성.
 
    ...          //pInv를 가지고 이것 저것 처리.
 
    delete pInv; //반드시 사용자가 객체 해제.
}
 
cs

 

그러나 pInv를 가지고 처리를 하는 도중 문제가 발생하여 delete 부분에 도달하지 않고 함수를 빠져나가거나

사용자가 실수로 delete 구문을 빼먹을 수도 있다.
createInvestment 함수로 얻어낸 자원이 항상 해제되도록 만들 방법은 객체가 소멸될 때 소멸자에서 메모리를

해제시키는 방법이다.

 

표준 라이브러리를 보면 auto_ptr이라는 것이 있는데 auto_ptr은 포인터와 비슷하게 동작하는 객체(스마트 포인터)로서,

가리키고 있는 대상에 대해 소멸자가 자동으로 delete를 불러주도록 설계되어 있다.

 

1
2
3
4
5
6
7
void func()
{
    std::auto_ptr<Investment> pInv(createInvestment()); //팩토리 함수를 통해 객체 생성.
 
    ...
}        //auto_ptr의 소멸자를 통해 pInv를 삭제한다.
        //그러므로 사용자가 직접 delete를 호출할 필요가 없다.
cs

 

자원 관리에 객체를 사용하는 방법의 중요한 두 가지 특징이 있다.


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

 상기 예제를 보면, createInvestment 함수가 만들어준 자원은 그 자원을 관리할 auto_ptr 객체를 초기화하는 데 쓰이고 있다.
 실제로, 이렇게 자원 관리에 객체를 사용하는 아이디어에 대한 용어도 자주 통용되는데 '자원 획득 즉 초기화' (RAII)라 부른다.

 

2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.

 

 소멸자는 어떤 객체가 소멸될 때 자동적으로 호출되기 때문에, 실행 제어가 어떤 경위로 블록을 떠나는가에 상관없이 자원 해제가

 제대로 이루어지게 된다.

 

auto_ptr은 자신이 소멸될 때 자신이 가리키고 있는 대상에 대해 자동으로 delete를 하기 때문에, auto_ptr은 오직 1개의 객체만

가리키는 것이 가능하다. 그래서 auto_ptr을 이용해서 객체를 복사하면 원본 객체는 null이 되버린다.

 

1
2
3
4
5
6
7
    std::auto_ptr<Investment> pInv1(createInvestment()); //팩토리 함수를 통해 객체 생성.
 
    std::auto_ptr<Investment> pInv2(pInv1); //pInv2는 pInv1이 가리키던 객체를 가리키고
                                            //pInv1은 null이 된다.
 
    pInv1 = pInv2;    //pInv1은 pInv2가 가리키던 객체를 가리키고
                    //pInv2는 null이 된다.
cs

 

이러한 특성때문에, STL 컨테이너의 경우에는 원소들이 '정상적인' 복사 동작을 가져야 하기 때문에, auto_ptr은 STL 컨테이너들의

원소로 허용되지 않는다.

 

auto_ptr을 쓸 수 없는 상황이라면, 그 대안으로 참조 카운팅 방식 스마트 포인터(RCSP)를 사용하면 좋다.

RCSP는 특정 자원을 가리키는 외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원을 자동으로 해제시킨다.
마치 가비지 컬렉션과 비슷해보이지만, 참조 상태가 고리를 이루는 경우(예로 서로 다른 두 객체가 서로를 가리키는 경우)에는

자원을 해제시킬수 없다는 점은 가비지 컬렉션과 다르다.

 

1
2
3
4
5
6
7
8
void Func()
{
    std::tr1::shared_ptr<Investment> pInv1(createInvestment());
 
    std::tr1::shared_ptr<Investment> pInv2(pInv1);
 
    pInv2 = pInv1;
}
cs

 

tr1::shared_ptr을 사용하기 위해서는 VS2008 sp1 이상이여야 하며, #include <Memory>를 선언해줘야 한다.

 

auto_ptr, tr1::shared_ptr은 자원 관리를 하는 몇 가지 방법들 중 하나일 뿐 중요한 점은 자원 관리 객체를 사용해서 자원을

관리하는 것이 중요하다는 것이다.

 

알아둬야 할 점이 한 가지 더 있다. auto_ptr 및 tr1:shared_ptr은 소멸자 내부에서 delete 연산자를 사용한다.

delete[] 연산자가 아니다!! 즉, 동적으로 할당된 배열에 대해 auto_ptr이나 tr1::shared_ptr을 사용하면 절대 안된다는 것이다.

문제는 컴파일 에러도 발생하지 않는다는 점이다.

C++ 표준 라이브러리에서는 동적 할당된 배열을 위한 스마트 포인터가 제공되지 않는다.

이유는 동적으로 할당된 배열은 이제 vertor 및 string으로 거의 대체할 수 있기 때문이다.

 

2줄 요약

 

 - 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는 RAII 객체를 사용하자.

 

 - 일반적으로 RAIi 클래스는 tr1::shared_ptr과 auto_ptr이 있다. tr1::shared_ptr이 복사 시의 동작이 직관적이기 때문에

   사용하기 더 좋다. auto_ptr은 복사되는 객체를 null로 만들어 버린다.

 

 

 

 

+ Recent posts