KoreanFoodie's Study

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

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 25. 16:11

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

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

 

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

int priority();

void processWidget(std::shared_ptr<Widget> pw, int priority)

...
// 함수를 호출해보자
processWidget(new Widget, priority());

하지만 위의 코드는 컴파일이 되지 않는다. 왜일까?

포인터를 받는 shared_ptr 의 생성자는 explicit 으로 선언되어 있기 때문에, 'new Widget' 표현식에 의해 만들어진 포인터가 shared_ptr 타입의 객체로 바뀌는 암시적인 변환이 일어날 수 없기 때문이다! 따라서 다음 코드는 컴파일이 된다.

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

하지만 위 문장은 매우 위험한 점이 숨겨져 있다.

먼저, shared_ptr 는 다음과 같은 부분으로 나뉘어 있다.

  1. 'new Widget' 표현식을 실행하는 부분
  2. shared_ptr 생성자를 호출하는 부분

그런데, processWidget 에는 priority( ) 함수가 호출되는 부분이 있는데, 다음과 같이 순서가 섞인다고 가정해 보자(인자를 처리하는 순서는 컴파일러마다 다르다).

  1. 'new Widget' 표현식을 실행하는 부분
  2. priority 호출
  3. shared_ptr 생성자를 호출하는 부분

만약 priority 를 호출하는 과정에서 예외가 발생한다면, "new Widget" 으로 만들어졌던 포인터가 유실될 수 있다! 즉, 자원이 생성되는 시점(1번) 과 자원이 자원 관리 객체로 넘어가는 시점(3번) 사이에 예외가 끼어들 수 있다는 것이다!

이 문제를 피하기 위해서는, Widget 을 생성해서 스마트 포인터에 저장하는 코드를 별도의 문장으로 만들고, 그 스마트 포인터를 processWidget 에 넘기기만하면 된다!

// 독립적인 문장으로 만들기
std::shared_ptr<Widget> pw(new Widget);
// 이제 자원 누출 걱정이 없다!
processWidget(pw, priority());

이제 컴파일러의 눈치를 보지 않는, 안전한 코드가 만들어졌다!

Comments