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

수민이슈·2023년 3월 22일
0

Effective C++

목록 보기
17/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 new로 생성한 객체스마트 포인터로 넣는 코드는 별도의 한 문장으로 만들자!
이렇게 안하면, 예외 발생 시 디버깅 하기 힘든 자원 누출이 발생할 수 있다


🖊️ new로 생성된 객체를 스마트 포인터에 저장할 때의 자원 누출 가능성

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

processWidget(new Widget, priority());

이런 경우에는, new Widget으로 생성한 포인터tr1::shared_ptr<Widget> 가 다른 타입이고,
그 포인터를 shared_ptr타입으로 바꿔주는 암시적인 변환이 없기 때문에
컴파일이 안된다.
shared_ptr의 생성자는 explicit으로 선언되어 있다..

explicit

: 컴파일러가 형변환 해주는 것을 막아준다.

그래서

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

이렇게 해줘야 한다.
(shard_ptr 타입으로 명시적 변환)

근데 이러한 상황에서는 자원이 누출될 가능성이 있다.

컴파일러는 processWidget 호출 코드를 만들기 전에 인자를 평가한다.
그래서 함수를 호출하기 전에, 컴파일러는 아래 세 가지 연산을 위한 코드를 만들어야 한다.

  • priority 호출
  • new Widget 실행
  • tr1::shared_ptr 생성자 호출

근데 C++ 컴파일러는 위 세 가지 연산의 수행 순서가 미정의되어있다.

아무래도, shared_ptr의 생성자의 인자로 new Widget한 결과가 들어가야 하니까
new Widget 표현식 실행이 tr1::shared_ptr 생성자 호출보다 선행되어야 한다.

근데,,
priority는 언제 호출되든 상관없잖아

그래서 만약에

  • new Widget 실행
  • priority 호출
  • tr1::shared_ptr 생성자 호출

순서대로 이렇게 수행된다고 했을 때,
만약 priority 호출에서 문제가 생긴다면,,

당연히 tr1::shared_ptr 생성자 호출은 불리지 않을 거고
그러면 new Widget으로 생성된 포인터는 .. 그냥 누출될거다.

왜그러냐면,

자원이 생성되는 시점과 그 자원이 자원 관리 객체로 넘어가는 시점 사이에 예외가 끼어들 수 있기 때문이다.

해결하려면,,

std::tr1::shared_ptr<Widget> pw(new Widget);

processWidget(pw, priority());

이렇게 한다면
pwpriority 호출 두 개의 순서만 왔다갔다하고,
생성되는 시점과 그 객체를 자원 관리 객체(shared_ptr)에 넣어주는 시점 사이가 방해받을 일은 없다.

그래서 자원이 누출될 일이 없는거당.


😊 느낀점

생성 시점, 자원 관리 객체로 넘겨주는 시점
두 시점 사이에 다른 일이 끼어들어서 자원이 누출되는 상황
디버깅한다고 생각하면 진심 끔찍하다
미리 미연에 방지하도록 하자,,

0개의 댓글