유리수를 나타내는 객체가 있다고 생각해보자.
operator*는 값을 반환함 => 객체의 생성, 소멸에 비용이 들어감 => 그래서 이걸 참조자로 반환하고 싶다. => 가능?
참조자는 그냥 메모리공간에 붙이는 또다른 이름이다. 즉 이미 존재하는 메모리 공간에 또 다른 이름을 붙이는 것이다.
만약 참조자로 반환하고 싶으면 반드시 이미 존재하는 Rational 객체의 참조자여야 한다. 이때 이 객체에는 곱셈에 대한 결과가 저장되어야 한다.
함수 수준에서 새로운 객체를 만든 방법은 딱 2가지이다.
- 스택에서 만듬(지역변수)
- 힙에서 만듬(동적 할당 메모리)
우선 전자의 방법부터 보자.
컴파일러도 병신같은 코드라고 주의를 주고 있다. 생성자가 호출되는게 싫어서 참조자를 반환할려는데 result가 생성된다? 노답, 그리고 실질적인 문제는 result에 대한 참조자가 반환되는데, result는 지역변수라 함수가 끝나면 소멸된다. => dangling reference 문제가 발생
후자의 방법을 보자.
역시 생성자가 한번 호출되는건 매한가지이다. 그리고 실질적인 문제는, new로 생성한 객체를 누가 delete로 지워주냐는 것이다. 여기서
이렇게 되면 new를 두번했기 때문에 delete도 두번해야 하는데, 사용자가 어떻게 new로 생성된 객체에 접근할 방법이 없다. (x * y)를 한번 delete 해줘야 하고, ((x*y)*z) 를 또 한번 해제해줘야하는데, 어떻게 함?
최악의 방법이 있는데, result를 static 객체로 함수 안에 정의 하는 것이다. => 생성자가 한번만 호출되니 개꿀??
밑의 연산을 보자. if 내의 비교문은 항상 참이 된다. 왜??
저걸 풀어서 쓰면 operator==( operator*(a,b), operator*(c,d) ) => operator==( result, result ) => 값이 항상 같을 수 밖에 없다!!! 같은 객체를 비교하는 것인데 어떻게 값이 안 같을 수 있나
새로운 객체를 반환해야 하는 함수를 작성하는 방법은 정해져있다. 바로 '새로운 객체를 반환하게 만드는것' 이다.
물론 여기에 반환값을 생성, 소멸시키는 비용이 들어 있지만 이것은 올바른 동작에 지불되는 작은 비용이다.
inline으로 call에 들어가는 cost까지 줄임
그리고 요새 컴파일러들은 몇몇 조건하에서는 최적화 메커니즘이 돌아가서 operator*의 반환값에 대한 생성, 소멸 동작이 안전하게 제거될 수 있다.(return value optimization, RVO) -> 생각보다 빠른 동작을 보임
- 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있으면 절대 하지말자.(항목 4에 지역 정적 객체에 대한 참조자를 반환하도록 설계된 올바른 코드 예제를 찾을 수 있다. 최소한 단일 스레드 환경에서는 통하는 설계이다.) => 항목 4의 tempDir() 참고
- 보통 정적 객체를 반환할때 참조자 반환이 쓰인다.
'Effective C++ > 4. 설계 및 선언' 카테고리의 다른 글
항목 23: 함수보다는 비멤버 비프렌드 함수와 더 가까워지자 (0) | 2021.04.24 |
---|---|
항목 22: 데이터 맴버가 선언될 곳은 private 영역임을 명심하자. (0) | 2021.04.24 |
항목 20: '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫다. (0) | 2021.04.24 |
항목 19: 클래스 설계는 타입 설계와 똑같이 취급하자. (0) | 2021.04.24 |
항목 18: 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2021.04.24 |