처리 우선순위를 알려 주는 함수가 있고, 할당된 객체에 대해 어떤 우순순위에 따라 처리를 적용하는 함수가 하나 있다고 가정해보자
int Priority();
void ProcessWidget(std::tr1::shared_ptr<Widget> pw, int priority);
자원 관리에는 객체를 사용하는 것이 좋다(13장 참고)는 점을 살려 ProcessWidget 함수는 동적 할당된 Widget 객체에 대한 스마트 포인터를 사용하도록 만들어졌다.
이렇게 만들어진 ProcessWidget 함수를 사용한다면
ProcessWidget(new Widget, Priority());
컴파일 자체도 안되는 코드다. 포인터를 받는 tr1::shared_ptr의 생성자는 explicit(*주 1)로 선언되어 있기 때문에, new Widget 표현식에 의해 만들어진 포인터가 tr1::shared_ptr 타입의 객체로 바꾸는 암시적 변환이 없다.
ProcessWidget(std::tr1::shared_ptr<Widget>(new Widget), Priority());
코드 자체는 돌아가지만, 문제점이 있다. 위 코드는 자원을 흘릴 가능성이 있다는 점이다.
컴파일러는 ProcessWidget 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘기는 인자를 평가(evaluate)하는 순서를 갖는다. 여기서 두 번째 인자는 Priority함수의 호출문밖에 없지만, 첫 번째 인자 std::tr1::shared_ptr<Widget>(new Widget)는 두 부분으로 나누어져 있다.
1.new Widget 표현식을 실행하는 부분
2.tr1::shared_ptr 생성자를 호출하는 부분
종합적으로 ProcessWidget 함수 호출이 이뤄지기 전에 컴파일러는 세 가지 연상을 위한 코드를 만들어야 한다.
1.Priority 함수를 호출한다.
2.new Widget을 실행한다.
3.tr1::shared_ptr 생성자를 호출한다.
그런데 각각 연산이 실행되는 순서는 컴파일러마다 다르다는게 문제가 된다.
(C++의 컴파일러는 매개변수의 평가 순서를 정하는 데 있어서 상당한 자유도를 갖고 있다. 자바 및 C# 등의 언어는 특정하게 고정되어 있어 C++의 다른 언어와의 차이점이다.)
new Widget 표현식은 tr1::shared_ptr 생성자가 호출되기 전에 호출되어야 한다는건 짐작할 수 있다. 그러나 Priority 함수의 호출은 처음이 될 수도 있고, 두 번째나 세 번째에 호출될 수도 있다.
만약 Priority 함수가 두 번째에 호출되기로 정해졌다면
1.new Widget을 실행한다.
2.Priority 함수를 호출한다.
3.tr1::shared_ptr 생성자를 호출한다.
이런 순서로 호출될 것이다.
만약 Priroty 함수 호출 부분에서 예외가 발생했다면 어떻게 될것인가?
이미 new Widget 표현식을 지났기에 Widget 객체는 생성이 되었고, 자원 누출을 막기 위해 준비한 tr1::shared_ptr에 저장되기 전이니까 자원 누출이 발생할 것이다.
정리하자면 자원이 생성되는 시점을 지나 그 자원이 자원 관리 객체로 넘어가는 시점 사이에 예외가 발생한 경우 문제가 생긴다는 것이다.
해결법 자체는 간단하다. 밖으로 빼면된다.
std::tr1::shared_ptr<Widget> pw(new Widget);
ProcessWidget(pw, Priority());
솔직히 이 악물고 에러 예제를 만들려고 했다는 느낌을 받는다.
객체를 함수에 집어넣을 때 함수 인자에서 객체를 생성한다는 방식은 나는 잘 안쓰기 때문이다. 어쩌다 운좋게 이런 문제에 걸린적은 없었지만, 알고 있어야 하는 점이라고 생각한다.
vector나 RBTree 구현할 때 나는 node를 함수 인자에서 생성하는게 맘에 안들어서 밖으로 빼버렸는데 예제를 보면 간혹 함수 인자에서 CreateNode 함수를 호출하는 예제를 몇몇 본적이 있다.
이것만은 잊지 말자
new로 생성한 객체를 스마트 포인터로 넣는 코드는 별도의 한 문장으로 만들자.
이것이 안되어 있으면, 예외가 발생될 때 디버깅하기 힘든 자원 누출이 초래될 수 있다.
'서적 정리 > Effective C++' 카테고리의 다른 글
21.함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자 (0) | 2021.12.14 |
---|---|
20.'값에 의한 전달'보다는 '상수객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫다 (0) | 2021.12.14 |
19.클래스 설계는 타입 설계와 똑같이 취급하자 (0) | 2021.12.14 |
18.인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2021.12.14 |
16.new 및 delete를 사용할 때는 형태를 반드시 맞추자 (0) | 2021.12.11 |
15.자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 (0) | 2021.12.11 |
14.자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 (0) | 2021.12.11 |
13.자원 관리에는 객체가 그만! (0) | 2021.12.10 |
댓글