본문 바로가기
프로그래밍/Effective C++

Chapter 3 자원 관리

by Ohdumak 2018. 4. 11.

2018/03/28 - [프로그래밍/Effective C++] - Chapter 2 생성자, 소멸자 및 대입 연산자


Chapter 3 자원 관리


프로그래밍 분야에서 자원(resource)이란, 사용을 일단 마치고 난 후에 시스템에 돌려주어야 하는 모든 것을 일컫는다. 가장 중요한 것은 "가져와서 썼으면 해제해야, 즉 놓아 주어야 한다"


항목 13: 자원 관리에는 객체가 그만!


특정 함수로 얻어낸 자원이 항상 해제되도록 만드는 방법은, 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 하며, 그 소멸자는 실행 제어가 함수를 떠날 때 호출되도록 만드는 것이다.


소프트웨어 개발에 쓰이는 상당수의 자원이 힙에서 동적으로 할당되고, 하나의 블록(block)혹은 함수 안에서만 쓰이는 경우가 잣기 때문에 그 블록 혹은 함수로부터 실행 제어가 빠져 나올 때 자원이 해제되는 게 맞다. auto_ptr은 이런 용도에 쓰라고 마련된 클래스이다. auto_ptr은 포인터와 비슷하게 동작하는 객체(스마트 포인터)로서, 가리키고 있는 대상에 대해 소멸자가 자동으로 delete를 불러주도록 설계되어 있다.


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

- 첫째 ,자원을 획득한 후에 자원 관리 객체에게 넘긴다.

 자원 관리에 객체를 사용하는 아이디어에 대한 업계 용어도 자주 통용된다. 자원 획득 즉 초기화(Resource Acquisition Is Initialization: RAII)라는 이름이다.

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


auto_ptr을 쓸 수 없는 상황이라면 그 대안으로 참조 카운팅 방식 스마트 포인터가 아주 괜찮다. RCSP는 특정한 어떤 자원을 가리키는(참조하는)외부 객체의 개수를 유지하고 있다가 그 개수가 0이 되면 해당 자원을 자동으로 삭제하는 스마트 포인터이다.


배열을 쓸 수 있는 auto_ptr이라든지 tr1:shared_ptr이 있으면 좋겠다고 생각하면 부스트에 boost::scoped_array와 boost::shared_array를 확인하자.


이것만은 잊지 말자!

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

- 일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptr 그리고 auto_ptr입니다. 이 둘 가운데 tr1::shared_ptr이 복사 시의 동작이 직관적이기 때문에 대게 더 좋다. 반면, auto_ptr은 복사되는 객체(원본 객체)를 null로 만들어 버린다.



항목 14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자


"RAII 객체가 복사될 때 어떤 동작이 이루어져야 할까?"

- 복사를 금지한다.

- 관리하고 있는 자원에 대해 참조 카운팅을 수행한다.

- 관리하고 있는 자원을 진짜로 복사한다.

- 관리하고 있는 자원의 소유권을 옮긴다.


이것만은 잊지 말자!

- RAII 객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고 가기 때문에, 그 자원을 어떻게 복사하느냐에 따라 RAII 객체의 복사 동작이 결정된다.

- RAII 클래스에 구현하는 일반적인 복사 동작은 복사를 금지하거나 참조 카운팅을 해 주는 선으로 마무리하는 것이다. 하지만 이 외의 방법들도 가능하니 참고해 두자.



항목 15: 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자


RAII 클래스를 실제 자원으로 바꾸는 방법으로서 명시적 변환을 제공할 것인지(get 멤버 함수 등) 아니면 암시적 변환을 허용할 것인지에 대한 결정은 그 RAII 클래스만의 특정한 용도와 사용 환경에 따라 달라진다. "맞게 쓰기에 쉽게, 틀리게 쓰기에는 어렵게" 만들어져야 한다.


RAII 클래스는 애초부터 데이터 은닉이 목적이 아니다. 원하는 동작(자원 해제)이 실수 없이 이루어지도록 하면 OK이다.


이것만은 잊지 말자!

- 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법을 열어 주어야 한다.

- 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능하다. 안전성만 따지면 명시적 변환이 대체적으로 더 낫지만, 고객 편의성을 놓고 보면 암시적 변환이 괜찮다.



항목 16: new 및 delete를 사용할 때는 형태를 반드시 맞추자


어떤 포인터에 대해 delete를 적용할 때, delete 연선자로 하여금 '배열 크기 정보가 있다'는 것을 알려줄 필요가 있다. 대괄호 쌍([])을 delete 뒤에 붙여 주면 '포인터가 배열을 가리키고 있구나'라고 가정하게 된다. 그렇지 않으면 그냥 단일 객체라고 간주한다.


new 표현식에 []를 썼으면, 여기에 대응되는 delete 표현식에도 []를 써야 한다는 아주 간단한 규칙이다. new 표현식에 []을 안 썼으면, 대응되는 delete 표현식에도 []을 안 쓰면 된다.


표준 C++ 라이브러리에는 string이라든지 vector 같은 좋은 클래스 템플릿이 있어서, 잘 활용하면 동적 할당 배열이 필요해질 경우가 거의 없기 때문이다.


이것만은 잊지 말자!

- new 표현식에 []를 썼으면, 대응되는 delete 표현식에도 []를 써야 한다. 마찬가지로 new 표현식에 []를 안 썼으면, 대응되는 delete 표현식에도 []를 쓰지 말아야 한다.



항목 17: new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자


한 문장 안에 있는 연산들보다 문장과 문장 사이에 있는 연산들이 컴파일러의 재조정을 받을 여지가 적기 때문에 위의 코드는 자원 누출 가능성이 없다.


이것만은 잊지 말자!

- new로 생성한 객체를 스마트 포인터로 넣는 코드는 별도의 한 문장으로 만들자. 이것이 안 되어 있으면, 예외가 발생될 때 디버깅하기 힘든 자원 누출이 초래될 수 있다.



728x90

댓글