17: new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자
처리 우선순위를 알려 주는 함수가 하나 있고, 동적으로 할당한 Widget 객체에 대해 어떤 우선순위에 따라 처리를 적용하는 함수가 하나 있다고 가정하자.
int priority()
void proceessWidget(str1::shared_ptr<WIdget> pw, int priority);
이렇게 만들어진 processWidget 함수를 호출하자.
processWidget(new Widget, priority());
위처럼 호출하면 컴파일이 안 된다. shared_ptr의 생성자는 explicit로 선언되어 있기 때문에, new Widget 으로 만들어진 포인터가 shared_ptr 타입의 객체로 바꾸는 암시적인 변환이 있을 리가 없기 때문이다. 아래의 코드는 컴파일된다.
proecessWidget(tr1::shared_ptr<Widget>(new Widget), priority);
그런데 이 문장은, 자원을 흘릴 가능성이 있다.
컴파일러는 processWidget을 호출하기 전에 매개변수로 넘겨지는 인자를 평가하는 순서를 밟는다. 여기서 첫 번째 인자 tr1::shared_ptr<Widget>(new Widget)은 두 부분으로 나누어져 있다.
- new Widget 표현식을 실행하는 부분
- shared_ptr 생성자를 호출하는 부분
함수 호출이 이루어지기 전에 컴파일러는 다음의 세 가지 연산을 위한 코드를 만들어야 한다.
- priority 호출
- new Widget 을 실행
- shared_ptr 생성자를 호출
그런데 여기서 컴파일러 제작사마다 연산이 실행되는 순서가 다르다는게 문제다. shared_ptr의 생성자로 넘어가야 하는 new Widget은 생성자가 실행되기 전에 호출되는게 당연하지만 priority는 두 번째나 세 번째에 호출될 수 있다.
- new Widget을 실행
- priority 호출
- shared_ptr 생성자를 호출
위의 순서로 진행될 때, priority의 호출 부분에서 예외가 발생한다면 new Widget으로 만들어졌던 포인터가 유실될 수 있다. shared_ptr에 저장되기도 전에 예외가 발생했기 때문이다. 자원이 생성되는 시점과 자원이 자원 관리 객체로 넘어가는 시점 사이에 예외가 끼어들 수 있다.
이런 문제를 피해가는 방법은 Widget을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장 하나로 만들고, 그 스마트 포인터를 processWidget에 넘기는 것이다.
tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
이것만은 잊지 말자!
- new로 생성한 객체를 스마트 포인터로 넣는 코드는 별도의 한 문장으로 만들자. 이것이 안 되어 있으면, 예외가 발생될 때 디버깅하기 힘든 자원 누출이 초래될 수 있다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 19: 클래스 설계는 타입 설계와 똑같이 취급하자 (0) | 2023.10.16 |
---|---|
[Effective C++] 18: 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2023.10.15 |
[Effective C++] 16: new 및 delete를 사용할 때는 형태를 반드시 갖추자 (1) | 2023.10.13 |
[Effective C++] 15: 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 (2) | 2023.10.12 |
[Effective C++] 14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 (1) | 2023.10.10 |