일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 크리티컬섹션
- C++최적화
- 람다사용정렬
- BFS
- UML관련
- stl
- 데이터애셋
- 언리얼엔진구조체
- 언리얼가비지컬렉터
- 알고리즘
- 강참조
- 스마트포인터
- moreeffectiveC++
- dataasset
- UE4 커스텀로그
- map
- UE_LOG
- C++
- 람다
- unorder_map
- 프로그래머스
- 정렬
- 애셋로드
- 자료구조
- 정렬알고리즘
- 델리게이트
- enumasByue
- 선택정렬
- 약참조
- UELOG
- Today
- Total
기억을 위한 기록들
[CPP-effective] 3-5 new로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한문장으로 만들자. 본문
[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가 출력 된 모습.
'C & CPP > Effective C++' 카테고리의 다른 글
[CPP-effective] 4-2 클래스 설계는 타입 설계와 똑같이 취급하자. (0) | 2021.04.12 |
---|---|
[CPP-effective] 4-1 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자. (0) | 2021.04.09 |
[CPP-effective] 3-4 new및 delete를 사용할 때는 형태를 반드시 맞춘다. (0) | 2021.04.08 |
[CPP-effective] 3-3 자원 관리 클래스에서 관리되는 자원은 외부에서 접근가능하게 하자. (0) | 2021.04.08 |
[CPP-effective] 3-2 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자. (0) | 2021.04.01 |