언어/C++

[Effective C++] new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자

지나가던 개발자 2020. 1. 17. 15:09
반응형

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

 

처리 우선순위를 알려 주는 함수가 하나 있고, 동적으로 할당한 Widget 객체에 대해 어떤 우선순위에 따라 처리를 적용하는 함수가 하나 있다고 가정합시다.

 

1
2
int prioirty();
void processWidget(std::tr1::shared_ptr<Widget> pw, int prioirty);
 

 

자원 관리에는 객체를 사용하는 것이 좋기 때문에 processWidget 함수는 동적 할당된 Widget 객체에 대해 스마트 포인터를 사용하도록 만들어졌습니다.

 

이렇게 만들어진 processWidget 함수를 이제 호출합니다.

 

1
processWidget(new Widget, prioirty());
 

 

이렇게 호출을 하면 컴파일이 되지 않습니다.

포인터를 받는 tr1::shared_ptr의 생성자는 explicit로 선언되어 있기 때문에, 'new Widget' 표현식에 의해 만들어진 포인터가 tr1::shared_ptr 타입의 객체로 바꾸는 암시적인 변환이 있을 리가 없기 때문입니다.

단, 아래의 코드는 컴파일 됩니다.

 

1
processWidget(std::tr1::shared_ptr<Widget>(new Widget), prioirty());
 

 

하지만 이 문장은 자원을 흘릴 가능성이 있습니다.

 

컴파일러는 processWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨지는 인자를 평가하는 순서를 밟습니다. 여기서 두 번째 인자는 prioriy 함수의 호출문밖에 없지만, 첫 번째 인자는 두 부분으로 나누어져 있습니다.

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

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

 

사정이 이렇기 때문에, processWidget 함수 호출이 이루어지기 전에 컴파일러는 다음의 세 가지 연산을 위한 코드를 만들어야 합니다.

- priority를 호출합니다.

- "new Widget"을 실행합니다.

- tr1::shared_ptr 생성자를 호출합니다.

 

그런데, 여기서 각각의 연산이 실행되는 순서는 컴파일러 제작사마다 다르다는 게 문제입니다.

 

예를 들어, 연산 순서가

1. "new Widget"을 실행합니다.

2. priority를 호출합니다.

3. tr1::shared_ptr 생성자를 호출합니다.

이렇게 정해졌다고 가정할 때 2번에서 예외가 발생한다면 "new Widget"으로 만들어졌던 포인터가 유실될 수 있습니다. 자원 누출을 막아 줄 줄 알고 준비한 tr1::shared_ptr에 저장되기도 전에 예외가 발생했으니까요.

 

결론을 말하자면, 자원이 누출될 가능성이 있는 이유자원이 생성되는 시점과 그 자원이 자원 관리 객체로 넘어가는 시점 사이에 예외가 끼어들 수 있기 때문입니다.

 

이런 문제를 피해 가는 방법은 간단합니다. Widget을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장 하나로 만들고, 그 스마트 포인터를 processWidget에 넘기는 것입니다.

 

1
2
3
std::tr1::shared_ptr<Widget> pw(new Widget);        // new로 생성한 객체를 슴트 포인터에 담는 코드를 하나의 독립적인 문장으로 만듭니다.
 
processWidget(pw, prioirty());                         // 이제는 자원 누출 걱정이 없습니다.
Colored by Color Scripter
 

 

꼭 잊지 말아야 할 것!

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

반응형