1
2
3
4
5
6
7
8
9
10
11
12
13
class Widget
{
   ...
};
 
int Calculate();
 
void processWidget(std::tr1::shared_ptr<Widget> pw, int Value);
 
void fucn()
{
    processWidget(new Widget, Calculate());
}
cs

 

위와 같이 스마트 포인터와 int 타입의 매개 변수를 가지는 processWidget 함수를 호출하려고 하면,

컴파일 에러가 발생한다.

 

포인터를 받는 tr1::shared_ptr의 생성자는 explicit으로 선언되어 있으므로, new Widget으로 생성된 포인터가

tr1::shared_ptr 타입의 객체로 바뀌는 암시적 변환이 이루어질 수 없기 때문이다.

 

그래서 하기와 같이 명시적 변환으로 함수를 호출해야 한다.

 

1
2
3
4
void fucn()
{
    processWidget(std::tr1::shared_ptr<Widget>(new Widget), Calculate());
}
cs

 

그런데 이 함수 호출 방식은 자원 관리 객체를 쓰고 있는데도 자원 누출이 될 가능성이 있다.

 

컴파일러는 processWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨지는 인자를 평가한다.

여기서 첫 번째 인자는 두 부분으로 나누어져 있는데 이 것이 문제의 원인이 될 수 있다.

 

첫 번째 인자가 나뉘는 두 부분.

 

 1. "new Widget" 표현식을 실행하는 부분.

 2. tr1::shared_ptr 생성자를 호출하는 부분.

 

그러므로 processWidget 함수 호출이 이루어지기 전에 컴파일러는 세 가지 연산 코드를 만들어야 한다.

 

 1. Calculate 함수 호출.

 2. "new Widget"을 실행.

 3. tr1::shared_ptr 생성자 호출.

 

그런데 문제는 각 각의 연산이 실행되는 순서가 컴파일러 제작사마다 다르다는 점이다.

자바 및 C#은 매개변수의 평가 순서가 특정하게 고정되어 있는 반면에 C++의 평가 순서는 높은 자유도를 가진다.

그래서 어떤 컴파일러의 경우에는 아래와 같은 순서로 진행될 수 있다.

 

 1. "new Widget"을 실행.

 2. Calculate 함수 호출.

 3. tr1::shared_ptr 생성자 호출.

 

만약 이러한 호출 과정을 진행하는 도중에, Calculate 함수에서 예외가 발생한다면 "new Widget'으로 만들어졌던 포인터가

스마트 포인터에 저장이 되지 않는 문제가 발생해 버린다.

즉, processWidget 호출 중에 자원이 누출될 가능성이 있는 이유는, 자원이 생성되는 시점("new Widget")과 새성한 자원이

자원 관리 객체로 넘어가는 시점 사이에 예외가 끼어들 수 있기 때문이다.

 

해결 방법

 

 - Widget을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장 하나로 만들고, 그 스마트 포인터를 ProcesWidget에

   넘기는 방법이다.

 

1
2
3
4
5
6
7
8
void fucn()
{
    std::tr1::shared_ptr<Widget> pw(new Widget); //new Widget으로 생성된 포인터를
                                                 //스마트 포인터에 저장하는
                                                 //별도의 문장.
 
    processWidget(pw, Calculate());
}
cs

 

1줄 요약

 

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

+ Recent posts