관리 메뉴

기억을 위한 기록들

[CPP-effective] 3-5 new로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한문장으로 만들자. 본문

C & CPP/Effective C++

[CPP-effective] 3-5 new로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한문장으로 만들자.

에드윈H 2021. 4. 8. 16:52

어떤 동적으로 할당한 Player객체에 대해 어떤 처리를 함수는 하나가 있다고 가정하자.

 

void doSomethingPlayer(shared_ptr<Player> p,int someValue)
{
	//...
}

그리고 어떤 값을 반환하는 함수가 있다고 하자.

int GetSomething()
{
	return 5;
}

자원 관리 함수를 사용하는 게 좋긴 하니까 스마트 포인터(유니크 포인터)로 인자를 받고 있다.

 

이렇게 만든 함수를 사용해보려 했으나

int main()
{	
	doSomethingPlayer(new Player, GetSomething()); //error!

	return 0;
}

 

컴파일 에러가 난다. 스마트포인터 생성자는 explicit로 선언되어 있기 때문에 'new Player' 표현식에 의해 만들어진 포인터가 shared_ptr타입의 객체로 바꾸는 암시적인 변환이 없기 때문이다.

에러내용!

하지만 아래와 같은 코드로 컴파일이 가능하긴 하다.

 

int main()
{	
	doSomethingPlayer(shared_ptr<Player>(new Player), GetSomething()); //ok

	return 0;
}

그런데 위의 코드도 문제 없이 컴파일은 되지만 자원을 흘릴 가능성이 있다는 것이다.

풀어보자면, 컴파일러는  doSomethingPlayer함수 호출 코드를 만들기 전에 우선 이 함수의 매개변수로 넘겨지는 인자를 평가(evaluate)하는 순서를 밟는다. 여기서 해당 인자(파라미터)는 

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

이렇게 진행이 된다. 그래서 doSomethingPlayer 함수 호출이 이루어지기 전에 컴파일러는 3가지의 연산을 진행한다.

 

1. GetSomething 함수 호출

2. new Player를 실행.

3. shared_ptr<Player> 생성자 호출

 

여기서의 문제점은 컴파일러 제작사마다 다르다는 게 문제이다. (자바 및 C#은 매개변수의 평가 순서가 특정하게 고정)

C++의 컴파일러의 경우엔 이들의 순서를 정하는 데 있어서 상당한 자유도를 갖고 있기 때문.

 

new Player표현식은 shared_ptr <Player> 생성자 호출이 되기전에 실행되어야한다. 그러나 GetSomething함수가 처음 될 수도 있고, 2번째나 혹은 3번째에 호출이 될 수도 있다. 어떤 컴파일러가 2번째라고 정했다면(효율면에서 더 괜찮은 코드를 만들 수 있는 방법일수도) 연산 순서는 바뀌게 된다.

 

1. new Player를 실행.

2. GetSomething 함수 호출

3. shared_ptr <Player> 생성자 호출

 

그러나, 만약에 GetSomething 호출에서 예외가 발생한다면, new Player로 만들어진 포인터가 유실될 수도 있다.

 

만든 자원(new Player)이 자원 관리 객체(shared_ptr <Player>)로 넘어가는 시점 사이에 예외가 끼어들 수 있기 때문이다.

 

이런 문제를 피하는 방법도 간단하다. 별도로 만드는 것이다.

 

int main()
{	
	shared_ptr<Player> p1(new Player);//별도로 생성

	doSomethingPlayer(p1, GetSomething());//이젠 걱정이 없다.
	return 0;
}

 

이렇게 되면 누출될 수 있는 가능성이 없어지게 된다.

 

 

정리

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

doSomethingPlayer함수를 실행하면, GetSomething 함수의 Hello가 출력되고, doSomethingPlayer의 world가 출력 된 모습.