이 글은 『Effective C++』를 읽고 개인적으로 공부한 내용을 정리한 기록입니다.
저는 컴퓨터공학을 전공하지 않았으며, 프로그래밍을 공부하는 과정에서의 이해와 생각을 정리하기 위해 글을 작성하고 있습니다.
따라서 내용 중 일부에 오류나 부정확한 설명이 있을 수 있으며, 피드백은 언제든지 환영합니다. 확인 후 수정하도록 하겠습니다.
전문적인 해설이 아닌 개인적 시선에서의 정리임을 참고하고 읽어주시면 감사하겠습니다.
[Effective C++ 정리 #17] 스마트 포인터를 사용할 때 new는 반드시 독립된 문장에서 처리합니다
스마트 포인터를 사용하면 자원 해제 문제를 거의 완벽하게 해결할 수 있습니다. 하지만 이번 아이템 #17에서는 한 가지 놓치기 쉬운 중요한 내용을 알려줍니다. 그것은 바로, 스마트 포인터를 사용해도 new 연산은 반드시 독립된 문장에서 처리해야 예외에 안전하다는 것 입니다.
문제 상황: processWidget 예제
다음과 같은 함수를 생각해봅니다:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
RAII를 활용하여 shared_ptr
로 자원을 관리하고 있습니다. 이제 우리는 다음처럼 호출하고 싶어집니다:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
일견 아무 문제가 없어 보입니다. new Widget
으로 객체를 생성하고, 곧바로 shared_ptr
에 넘겨서 소유권을 위임합니다. 이러면 자원 누수 걱정은 없는 것처럼 보입니다.
하지만 **예외가 발생하면 심각한 문제가 발생할 수 있습니다**.
C++는 인자 평가 순서를 보장하지 않습니다
C++에서는 함수 인자의 평가 순서가 명시되어 있지 않습니다. 따라서 컴파일러는 다음 작업들을 어떤 순서로든 수행할 수 있습니다:
priority()
호출new Widget
실행shared_ptr
생성자 호출
물론 shared_ptr
생성자는 new Widget
의 결과를 받아야 하므로 최소한 new Widget
→ shared_ptr
생성자 순서는 보장됩니다. 하지만 priority()
는 어디서든 호출될 수 있습니다. 심지어 new Widget
과 shared_ptr
생성 사이에 끼어들 수도 있습니다.
예외 발생 시 발생하는 치명적 자원 누수
만약 다음 순서로 실행된다면 어떻게 될까요?
new Widget
실행 — 객체 메모리 할당priority()
호출 중 예외 발생
이 경우 new Widget
이 생성한 포인터는 아직 shared_ptr
에 전달되지 않았습니다. 따라서 예외가 발생하면 이 포인터는 아무도 관리하지 않게 되어 메모리 누수(resource leak)가 발생합니다.
**스마트 포인터를 사용해도 예외 발생 위치에 따라 자원이 유실될 수 있다는 사실**을 꼭 기억해야 합니다.
해결책: new는 반드시 독립된 문장에서 수행합니다!
이 문제를 해결하는 방법은 간단합니다. new
연산과 shared_ptr
생성은 독립된 문장으로 분리합니다:
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
이렇게 하면 new Widget
과 shared_ptr
생성이 완전히 끝난 후에 priority()
가 호출되므로 예외가 발생해도 shared_ptr
가 자원을 관리하고 있어 안전합니다.
스마트 포인터를 사용한다고 해서 모든 예외 안전성이 보장되는 것은 아닙니다. **스마트 포인터에 소유권이 넘어가기 전까지는 여전히 위험 구간이 존재합니다.** 따라서 본 아이템에서는 그 위험 구간을 어떻게 제거하는지 말해주고 있습니다.
핵심 요약
- new로 생성한 객체를 스마트 포인터에 넘길 때는 반드시 독립된 문장에서 처리한다.
감사합니다.