자원 관리
C++에서 말하는 자원 관리는 대부분 메모리 누수를 방지하기 위한 방법에 대해서 이야기한다고 생각한다. 당연히 이번에도 그러한 내용을 다룬다.
class Investment { ... };
void fun()
{
Investment *pInv = createInvestment(); // 팩토리 함수 호출
...
delete pInv;
}
해당 코드를 보게 되면 문제없이 pInv를 메모리에서 해제하는 것처럼 보이지만, 만약 … 부분, 즉 중간 부분에 루프문이나 예외문과 같은 코드를 작성하여 도달하지 못하게 된다면 메모리 누수가 발생하게 된다.
스마트 포인터(smart pointer): auto_ptr
소프트웨어 개발에 쓰이는 상당수의 자원이 힙에서 동적으로 할당되고, 하나의 블록(block) 혹은 함수 안에서만 쓰이는 경우가 잦기 때문에 그 블록 혹은 함수로부터 실행 제어가 빠져나올 때 해제시키기 위해 auto_ptr을 사용한다.
class Investment { ... };
void fun()
{
std::auto_ptr<Investment> pInv(createInvestment()); // 팩토리 함수 호출
...
}
이러한 방법은 간단하지만 중요한 두 가지 특성을 가질 수 있다. 첫째, 자원을 획득한 후에 자원 관리 객체에게 넘긴다. 둘째, 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.
auto_ptr의 문제점
std::auto_ptr<Investment>
pInv1(createInvestment());
std::auto_ptr<Investment> pInv2(pInv1);
pInv1 = pInv2; // pInv2는 null
auto_ptr은 유별난 특성을 지니는데, 어떤 객체를 가리키는 auto_ptr이 둘 이상 되면 절대 안 되기 때문에 객체를 복사하게 되면 원본 객체는 null로 만든다.
참조 카운팅 방식 스마트 포인터(reference-counting smart pointer: RCSP)
RCSP는 특정한 어떤 자원을 가리키는(참조하는) 외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원을 자동으로 삭제하는 스마트 포인터 입니다. 이러한 특징은 가비지 컬렉션(garbage collection)의 그것과 상당히 흡사합니다. 단, 참조 상태가 고리를 이루는 경우(예를 들면 다른 두 객체가 서로를 가리키고 있다든지)를 없앨 수 없다는 점은 가비지 컬렉션과 다릅니다.
void fun()
{
...
std::tr1::shared_ptr<Inverstment> pInv1(createInvestment());
std::tr1::shared_ptr<Inverstment> pInv2(createInvestment());
pInv1 = pInv2;
...
}
TR1에서 제공되는 tr1::shared_ptr이 대표적인 RCSP로, 이를 통해 위와 같이 작성할 수 있다. 이러한 코드는 참조를 카운팅하는 방식으로 복사 동작이 예상대로 이루어진다.
주의 사항
std::auto_ptr<std::string> aps(new std::string[10]);
std::tr1::shared_ptr<int> spi(new int[1024]);
auto_ptr 및 tr1::shared_ptr은 소멸자 내부에서 delete 연산을 사용한다. 즉, 배열을 위해 delete[] 연산을 따로 하지 않는다는 것이다.
추가로 C++ 표준 라이브러리에서는 동적으로 할당된 배열을 위해 준비된 auto_ptr 혹은 tr1::shared_ptr 같은 클래스가 제공되지 않는다. 심지어 TR1에서도 마찬가지다.
이는 동적으로 할당된 배열이 이제 vector 및 string으로 거의 대체될 수 있기 때문이다. 이러한 기능을 사용하고 싶다면 Boost에 boost::scoped_array와 boost::shared_array가 있다.
자, 이제 마지막 잔소리입니다. 앞에서 본 createInvestment 함수의 반환 타입이 포인터로 되어 있는데, 이 부분 때문에 문제가 생길 수 있음을 지적하고 싶습니다. 반환된 포인터에 대한 delete 호출을 호출자 쪽에서 해야 하는데, 그것을 잊어버리고 넘어가기 쉽기 때문입니다(auto_ptr 혹은 tr1::shared_ptr을 써서 delete를 수행한다고 하더라도, createInvestment의 반환 값을 스마트 포인터에 저장해야 한다는 점만은 여전히 기억하고 있어야 하거든요). 이문제를 어떻게든 해결하려면 createInvestment를 수술해서 인터페이스를 고쳐야 하는데, 항목 18에서 해 두었으니 한 번 보세요.'C++ > Effective C++' 카테고리의 다른 글
| [C++] 12. 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2025.12.08 |
|---|---|
| [C++] 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 (0) | 2025.11.19 |
| [C++] 10. 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2025.11.19 |
| [C++] 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2025.11.18 |
| [C++] 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2025.11.17 |