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

Jangmanbo·2023년 4월 4일
0

Effective C++

목록 보기
17/33

동적 할당한 객체를 스마트 포인터에 저장하기

// 처리 우선순위를 반환하는 함수
int priority();

// 동적 할당한 Widget 객체를 우선순위에 따라 처리하는 함수
void processWidget(std::shared_ptr<Widget> pw, int priority);

자원을 관리할 때는 객체를 사용하면 좋다고 배웠다.(항목 13)
따라서 processWidget 함수를 동적 할당된 Widget 객체에 대한 스마트 포인터를 사용하도록 만들었다.

컴파일 에러!

processWidget(new Widget, priority());

processWidget 함수를 호출하는 이 코드는 컴파일 에러가 발생한다.
shared_ptr는 생성자가 explicit으로 선언되어 있기 때문에 new Widget으로 만들어진 포인터가 shared_ptr 타입 객체로 바뀌는 암시적이 변환이 이루어지지 않기 때문이다.

정상 동작?

processWidget(shared_ptr<Widget>(new Widget), priority());

정상적으로 컴파일이 되려면 명시적으로 new Widget으로 만들어진 포인터를 shared_ptr 타입 객체로 변환해야 한다.

그러나 이 코드는 자원을 흘릴 가능성이 있다.


함수의 인자 평가 순서는 랜덤

컴파일러는 processWidget 호출 코드를 만들기 전에 먼저 processWidget 함수의 매개변수로 넘겨진 인사를 평가한다.

이때 두 번째 인자는 priority 호출 뿐이지만, 첫 번째 인자는 두 부분으로 나누어져 있다.

  • 첫번째 인자 : shared_ptr<Widget>(new Widget)
    • new Widget 표현식 실행
    • shared_ptr 생성자 호출
  • 두번째 인자 : priority()
    • priority 호출

그런대 이 세 가지 연산이 실행되는 순서는 컴파일러 제작사마다 다르다. (특히 C++은 평가 순서에 대해 상당한 자유도를 갖고 있다.)
물론 shared_ptr의 생성자 호출이 new Widget 실행 이후라는 것은 고정이지만, priority 호출은 처음, 혹은 두 번째, 아니면 세 번째에 호출될 수 있다.

  1. new Widget 표현식 실행
  2. priority 호출
  3. shared_ptr 생성자 호출

만약 다음과 같이 컴파일러가 priority 호출을 두 번째로 정했다면 자원이 누출될 가능성이 생긴다.
priority 호출에서 예외가 발생한다면, new Widget으로 생성한 포인터가 유실되기 때문이다.


해결법

자원 누출을 막으려면, 자원이 생성되는 시점과 그 자원이 자원 관리 객체로 넘어가는 시점 사이에 예외가 끼어들지 못하도록 해야 한다.

// new로 생성한 객체를 스마트 포인터에 저장하는 코드를 별도의 문장으로 분리
shared_ptr<Widget> pw(new Widget);	

processWidget(pw, priority());	// 자원 누출 가능성 X

자원을 스마트 포인터에 저장하는 코드를 별도의 문장으로 만들자.


정리
함수의 인자 평가는 랜덤이므로 자원 누출의 가능성을 없애려면 자원을 생성하고 스마트 포인터에 저장하는 코드를 별도의 문장으로 만들자

0개의 댓글