- new로 생성한 객체를 스마트 포인터에 넣는 코드는 한 문장으로 만들어라.
그렇지 않으면, 예외가 발생될 때 자원 누출 가능성이 생긴다.
새로 생성한 객체를 스마트 포인터로 관리할 때 주의점
자원 누출을 막기 위해 스마트 포인터를 사용하는 것은 좋은 방법이다.
하지만, 이 방법을 사용하더라도 자원 누출이 발생할 수 있다.
int priority(); // 우선순위를 알려주는 함수
void processWidget(std::tr1::shread_ptr<Widget> pw, int priority); // 우선순위에 따라 처리
우선순위에 따라 어떠한 작업을 하는 함수가 있다고 가정하자.
processWidget(new Widget, priority());
이렇게 함수를 사용할 수 없다. tr1::shared_ptr의 생성자는 explicit으로 선언되어 있기 때문에 암시적 변환이 불가능하다.
따라서 'new Widget' 표현식에 의해 만들어진 포인터가 tr1::shared_ptr로 변환되지 않기 때문에 컴파일이 되지 않는다.
그렇다면 다음과 같이 사용해야 한다.
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
문제가 없어보이지만 사실 함정이 있다.
컴파일러는 processWidget 호출 코드를 만들기 전에 매개변수로 넘겨지는 인자를 평가한다.
processWidget의 매개변수를 보면 new Widget을 tr1::shread_ptr로 감싸는 첫 번째 매개변수와 priority 함수의 호출문인 두 번째 매개변수로 구성되어 있다.
즉, 컴파일러는 다음과 같은 연산들을 실행한다.
- priority 함수 호출
- new Widget으로 객체 생성
- tr1::shared_ptr 생성자 호출
그런데 이러한 연산이 실행되는 순서는 컴파일러마다 다르다는 게 문제가 된다.
new Widget이 tr1::shared_ptr의 생성자가 호출되기 전보다 먼저 실행되는 것은 맞지만 priority 함수의 호출 순서는 상관이 없다는 말이 된다.
예를 들어보자.
- new Widget 실행
- priority 함수 호출
- tr1::shared_ptr 생성자 호출
만약 컴파일러가 위와 같은 순서로 연산을 실행한다고 가정했을 때, priority 함수를 실행하던 중 예외가 발생하면 new Widget으로 만들어졌던 포인터가 유실된다.
정리하자면, 자원이 생성되는 시점과 그 자원이 자원 관리 객체로 넘어가는 시점은 동일해야 한다.
std::tr1::shared_ptr<Widget> pw(new Widget); // 자원 생성과 자원 관리 객체 생성 사이에 연산이 없게
processWidget(pw, priority());
이렇게 되면 컴파일러에 의해 연산들이 재조정 받지 않기 때문에 자원 누출의 가능성을 없앨 수 있다.