자원 관리에는 객체가 그만!
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이 있다.
'C++ 심화' 카테고리의 다른 글
[C++] new 및 delete의 사용 (0) | 2016.12.25 |
---|---|
[C++] 자원 관리 클래스의 복사 동작 (0) | 2016.12.22 |
[C++] 객체의 복사 (0) | 2016.12.20 |
[C++] opeator=의 자개대입에 대한 처리 (0) | 2016.12.20 |
[C++] 대입 연산자와 *this 참조자 (0) | 2016.12.19 |