로또
13. 자원 관리에는 객체가 그만! 본문
다루는 내용
스마트 포인터를 이용한 자원 관리
문제 상황
class Investment{...}; // 여러 형태의 투자 클래스의 최상위 클래스
class InvestmentFactory{ // 투자 클래스 계통에 대해 객체를 동적할당하고 반환해주는 클래스
public:
static Investment* CreateInvestment(){
return new Investment;
}
};
void f(){
Investment* p_inv = InvestmentFactory::CreateInvestment();; // 객체 생성
... // 할당받은 p_inv 사용
delete p_inv; // 객체 해제
}
위와 같은 코드가 존재할 때, f()에서 Investment 객체를 할당받은 후, 항상 delete문이 호출되는가?
p_inv를 사용하다가 return문이 실행되거나, 사용 중 continue 혹은 goto, 예외 등이 발생하면 delete는 호출되지 못하고 p_inv가 가리키는 객체는 할당되지 못한 채 메모리에 상주하게 될 것이다.
이러한 문제를 해결하기 위해서, 자원을 객체에 넣고 그 자원의 해제를 소멸자가 담당하도록 만든다.
C++에 의해 scope를 벗어난 객체는 소멸자가 호출되기 때문에 위와 같은 문제 상황에서도 자원의 해제를 보장한다.
스마트 포인터
#include <memory>
using namespace std;
void f(){
unique_ptr<Investment> p_inv(InvestmentFactory::CreateInvestment());
...
// unique_ptr의 소멸자를 통해 자원 해제.
}
문제 상황의 함수 f를 스마트 포인터 중 하나인 unique_ptr을 사용하는 것으로 변경해주었다. (책에서는 auto_ptr을 사용함)
위 코드에서는 자원 관리에 객체를 사용하는 방법의 두 가지 특징을 엿볼 수 있다.
1. 자원을 획득한 후에 자원 관리 객체에게 넘긴다.
CreateInvestment()를 통해 획득한 자원은 unique_ptr 객체인 p_inv를 초기화하는데 사용하고 있다.
이렇게 자원 관리에 객체를 사용하는 아이디어에 대한 용어가 존재한다. 바로 RAII(Resource Acquision is Initialization) 이다. "자원의 획득은 즉 초기화이다." 라는 뜻으로, 자원 획득과 자원 관리 객체의 초기화가 한 문장에서 이루어지는 것이 일상적이기 때문이다.
2. 자원 관리 객체는 자신의 소멸자를 사용해서 자원이 확실히 해제되도록 한다.
소멸자는 어떤 객체가 소멸될 때(유효 범위를 벗어나는 등) 자동적으로 호출되기 때문에, 실행 제어가 어떤 이유로 인해 블록을 떠나는가에 상관없이 자원 해제를 보장할 수 있다.
현재, 스마트 포인터에는 unique_ptr, shared_ptr, weak_ptr 등을 제공하고 있는데, 이와 관련해서 다룬 글이 존재하니 다음 링크를 참조하자.
https://lotto12.tistory.com/19
이러한 스마트 포인터들은 소멸자 내부에서 delete [ ] 연산자가 아닌, delete 연산을 사용한다. (두 연산의 차이에 대해서는 이후 등장할 "16. new 및 delete를 사용할 떄는 형태를 반드시 맞추자." 에서 자세히 다룰 예정이다.) 때문에 동적으로 할당한 배열에 대해서 스마트 포인터를 사용하면 문제가 발생할 여지가 분명하나 컴파일 에러가 발생하지 않으니 주의하자.
또한, 동적 할당된 배열은 vector나 string 등으로 대체가 가능하므로 이를 위해 준비된 스마트 포인터 객체는 존재하지 않는다. (물론 부스트 라이브러리에 boost::scoped_array, boost::shared_array가 존재한다고 한다.)
이것만은 잊지 말자!
- 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고 소멸자에서 자원을 해제하는 RAII 객체를 사용하자.
- 일반적으로 널리 쓰이는 RAII 클래스는 unique_ptr, shared_ptr이다. 이 둘 중 shared_ptr이 복사 시 동작이 직관적이므로 대개 더 좋다. 반면 unique_ptr은 복사되는 객체를 null로 만들어버린다.
'책 > Effective C++' 카테고리의 다른 글
| 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자. (0) | 2023.11.13 |
|---|---|
| 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자. (0) | 2023.11.11 |
| 12. 객체의 모든 부분을 빠짐없이 복사하자. (0) | 2023.11.08 |
| 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자. (0) | 2023.11.08 |
| 10. 대입 연산자는 *this의 참조자를 반환하게 하자. (0) | 2023.11.08 |