로또
17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자. 본문
다루는 내용
하나의 문장에서는 컴파일러에 의해 연산 실행 순서가 재조정될 수 있어 자원 누출의 가능성이 있다. 이를 해결하는 방법을 알아보자.
문제 상황
다음과 같이 처리 우선순위를 알려주는 함수가 있고, 동적으로 할당한 Widget 객체에 대해 어떤 우선순위에 따라 처리를 적용하는 함수가 있을 때, 해당 함수를 호출하는 코드가 있다.
int GetPriority(); // 우선순위를 알려주는 함수
void ProcessWidget(shared_ptr<Widget> pw, int priority); // Widget 객체에 대해 처리하는 함수
int main(){
ProcessWidget(new Widget, GetPriority()); // 함수 호출
}
위와 같은 코드는 먼저, 정상적으로 컴파일되지 않는다.
Widget* 에서 shared_ptr<Widget*>으로 암시적 형 변환이 일어난다고 생각할 수 있지만 shared_ptr의 생성자는 explicit으로 선언되어 있으므로 그렇지 않다.
컴파일이 불가능하므로, 다음과 같이 수정하자.
int main(){
ProcessWidget(shared_ptr<Widget>(new Widget), GetPriority()); // 함수 호출
}
컴파일도 가능하고 정상적으로 작동하는 것처럼 보이나, 해당 문장은 자원을 흘릴 가능성이 여전히 존재한다.
컴파일러는 ProcessWidget 함수의 호출 코드를 생성하기 전에, 우선 해당 함수의 매개변수로 넘겨지는 인자를 평가하는 순서를 밟는다.
여기서 두 번째 인자는 GetPriority 함수의 호출문이 전부이지만, 첫 번째 인자는 두 부분으로 나누어 볼 수 있다.
- new Widget 표현식을 실행하는 부분
- shared_ptr 생성자를 호출하는 부분
때문에, ProcessWidget 함수 호출 이전에, 컴파일러는 다음의 세 가지 연산을 위한 코드를 생성해야 한다.
- GetPriority 함수를 호출한다.
- new Widget을 실행한다.
- shared_ptr 생성자를 호출한다.
여기서 위 세 연산이 실행되는 순서는 컴파일러의 제작사마다 다르다. C++ 컴파일러는 매개변수의 평가 순서가 특정하게 고정되어있는 Java, C#과 달리 이들의 순서를 정하는데 있어 상당한 자유도를 갖고 있다.
물론, new Widget은 shared_ptr 생성자의 매개변수로 사용될 것이므로 new Widget이 shared_ptr의 생성자보다 앞서 실행될 것이다. 하지만 GetPriority 함수는 언제든 호출될 수 있다.
만약, 다음과 같은 순서로 호출되었다고 가정해보자.
- new Widget 을 실행한다.
- GetPriority 함수를 호출한다.
- shared_ptr 생성자를 호출한다.
이 때, GetPriority() 함수를 호출하는 중 예외가 발생했다면 new Widget으로 만들어진 포인터가 유실될 수 있다. 자원이 shared_ptr에 의해 관리되기 전이기 때문이다.
해결 방법
이러한 문제를 피해가기 위해 Widget을 생성해서 스마트 포인터에 저장하는 코드는 다음과 같이 별도의 한 문장으로 만들어야 한다.
int main(){
shared_ptr<Widget> pw(new Widget);
ProcessWidget(pw, GetPriority());
}
위처럼 작성하는 것은 한 문장 안에 있는 연산들보다 문장과 문장 사이에 있는 연산들이 컴파일러의 재조정을 받을 여지가 적으므로 자원 누출의 가능성이 없다.
이것만은 잊지 말자!
- new로 생성한 객체를 스마트 포인터로 넣는 코드는 별도의 한 문장으로 만들자. 그렇지 않으면 예외가 발생할 때, 디버깅하기 힘든 자원 누출이 초래될 수 있다.
'책 > Effective C++' 카테고리의 다른 글
| 19. 클래스 설계는 타입 설계와 똑같이 취급하자. (0) | 2023.11.15 |
|---|---|
| 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자. (1) | 2023.11.14 |
| 16. new 및 delete를 사용할 때는 형태를 반드시 맞추자. (0) | 2023.11.13 |
| 15. 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자. (0) | 2023.11.13 |
| 14. 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자. (0) | 2023.11.11 |