프로그래밍 분야에서 말하는 자원은 여러가지가 존재한다.
대표적으로 동적 할당되는 메모리 외에도 파일 서술자 ( File Descriptor ), 뮤텍스 ( Mutex ), GUI, 폰트, 브러시, 데이터 베이스 연결 및 네트워크 소켓, 여러 Handler 역시 모두 자원이라고 할 수 있다.
앞서 말한 모든 자원들에는 절대적이고 공통된 규칙이 하나 존재하는데, 모든 자원은 사용된 이 후에 반드시 해제 해주어야 한다는 점이다.
이 간단한 규칙을 지키는 것이 굉장히 간단해 보이지만 실상은 그렇지 않다.
아래의 예시를 보자.
class Investment {...} ;
Investment* createInvestment(); // Investment 객체에 대한 팩토리 함수
위와 같이 객체 포인터를 반환하는 팩토리 함수가 있다고 가정해보자.
이것을 사용한다면 아래와 비슷한 형식의 코드가 사용될 것이다.
void someFunction()
{
Investment *pInv = createInvestment();
// ... some process...
delete pInv;
}
어쩌면 당연한 말이겠지만, 객체에 대한 해제는 해당 객체를 사용하려고 호출한 호출자(caller)가 하는 것이 맞다.
그렇기 때문에 someFunction 내부에서 delete pInv; 해당 코드가 사용된 것이다.
그런데 문제는 delete 코드가 무조건 실행된다는 보장이 없다는 점인데,
some process 내부에서 return, 예외 발생, go to 등의 어떠한 일이 일어날지 장담할 수 없기 때문이다.
물론 그러한 상황을 방지하기 위해서 하나하나 모두 따져가면서 프로그램을 작성한다면 괜찮겠지만,
오랜 시간 유지 보수가 이루어지고 프로그래머가 몇 번이나 바뀐 상황이라면 사실상 모든 구조를 파악하기는 힘들다.
더군다나 delete 문이 실행되지 않아서 발생하는 것은 메모리 문제라는 재앙 덩어리이다.
이러한 문제를 해결하는 간단한 방식이 존재한다.
사용하는 자원을 객체에 넣어서 해당 자원에 대한 해제를 소멸자가 맡도록 하는 것이다.
그리고 이러한 용도로 설계된 것이 auto_ptr, 이른바 smart pointer 이다.
void someFunction()
{
std::auto_ptr<Investment> pInv(createInvestment());
// ... some process...
// delete pInv;
}
auto_ptr은 하나의 클래스이고 인자로 받은 객체를 가리키고 있다가 소멸자에서 자동으로 해당 객체에 대해 delete를 호출하도록 설계되어 있다.
위의 예제에서 자원 관리에 객체를 사용하는 방법의 두 가지 중요한 특징을 볼 수 있다.
RAII 개념을 설명하기 위해서 auto_ptr 을 사용했는데, 사실 auto_ptr은 현재는 사용되지 않는 스마트 포인터이다.
문제점은 크게 2가지가 존재한다.
std::auto_ptr<Investment> pInv1(createInvestment());
std::auto_ptr<Investment> pInv2(pInv1); // pInv1 = null
pInv1 = pInv2; // pInv2 = null
물론 해당 방식이 유용한 상황이 존재할 수 있지만 강제성을 띈다는 점이 상식에서 벗어난다고 할 수 있다.
이렇듯 위의 2가지 치명적인 단점으로 인해서 c++11 부터는 해당 개념을 완전히 대체하는 unique_ptr 개념이 대신 사용된다.
더 자세한 내용은 아래의 링크를 참고
Is it possible to use a C++ smart pointers together with C's malloc?
또 하나의 중요한 스마트 포인터가 존재하는데, shared_ptr 이라는 참조 카운팅 방식 스마트 포인터이다. ( reference-counting smart pointer : RCSP )
특정한 자원을 가리키는 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원을 자동으로 삭제하는 방식으로 동작한다.
일종의 Garbage Collection 과 흡사하게 동작하지만, 참조 상태가 고리 ( circle ) 를 이루는 경우는 없앨 수 없다는 차이점이 존재한다.
문제점은 shared_ptr 역시 auto_ptr과 마찬가지로 배열 형식의 자원에 대해서는 제대로된 자원 해제를 지원하지 않는다.
사실 c++ 표준 라이브러리에서는 동적 할당된 배열을 위해 준비된 자원 관리 객체를 따로 제공해주지 않는다.
동적 할당 배열은 사실상 vector, string 으로 모두 대체가 가능하기 때문이다.
( boost 라이브러리의 scoped_array, shared_array 가 해당 기능을 가지고 있다. )
"자원 관리를 객체를 통해서 하자" 라는 지침을 설명하기 위해서 스마트 포인터 개념을 소개했지만, 이것은 그저 하나의 사용 예시에 불과하다.
경우에 따라서는 자신이 직접 자원 관리 클래스를 만들어서 사용해야 한다.
항목 15) 자원 관리 클래스에서 관리되는 자원은 외부 접근을 차단하자 (0) | 2021.12.05 |
---|---|
항목 14) 자원 관리 클래스의 복사 동작에 대해서 (0) | 2021.12.05 |
항목 12) 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2021.12.05 |
항목 11) operator= 에서 자기 대입 상황에 대한 처리를 반드시 하자 (0) | 2021.12.05 |
항목 10) 대입 연산자는 *this의 참조자 반환을 지향하자 (0) | 2021.12.05 |
댓글 영역