Effective C++/Effective C++
[Effective C++] 21. 참조자를 리턴하면 안되는 상황
김디트
2024. 6. 14. 11:35
728x90
항목 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
모든 코드에 '참조에 의한 전달'을 반영하려들면 안 되는 이유
-
class Rational { public: Rational(int numerator = 0, int denominator = 1); ... private: int n, d; friend const Rational operator* (const Rational& lhs, const Rational& rhs); }; ... Rational a(1, 2); // a = 1/2 Rational b(3, 5); // b = 3/5 Rational c = a * b; // c는 3/10이 되어야 한다.
- 참조자는 존재하는 객체에 붙는 이름일 뿐이다.
- 즉, 참조자를 리턴하려면 이미 존재하는 Rational 객체의 참조자여야 한다.
- operator*가 참조자를 리턴한다면
- 위 예제, c = a * b 에서 a * b의 결과로 도출되는 Rational은 언제 생성된 것을 리턴할 것인가?
참조자 반환을 위한 여러 가지 시도
- 스택에 새로운 객체를 만드는 방법
-
const Rational& operator* (const Rational& lhs, const Rational& rhs) { Rational result(lhs.n * rhs.n, lhs.d * rhs.d); // 스택 생성 return result; }
- 스택 객체는 참조자 반환 시점에는 소멸하게 되는데, 그렇다면 이미 소멸된 객체의 참조자가 반환된다는 뜻이다.
- 생성자가 불리는 걸 피하고자 참조자 반환을 했을텐데, 결과적으론 객체를 생성하는 꼴이 된다.
-
- 힙에 새로운 객체를 만드는 방법
-
const Rational& operator* (const Rational& lhs, const Rational& rhs) { Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d); return *result; };
- 여전히 생성자를 한번 호출한다.
- 메모리 누수의 문제가 있다. 이 힙 생성에 대한 뒷처리(delete를 짝지어주기)가 힘들어지기 때문이다.
-
Rational w, x, y, z; w = x * y * z; // delete를 어떻게 짝지어줄 것인가?
-
-
- 함수 내에 정적 객체를 정의하는 방법
-
const Rational& operator* (const Rational& lhs, const Rational& rhs) { static Rational result; // 반환에 사용할 정적 객체 result = ...; return result; }
- 스레드 안정성 문제가 있다.
- 해당 함수의 리턴값들을 비교하는 코드에서 늘 true를 반환하게 된다.
-
Rational a, b, c, d; ... if((a * b) == (c * d)) { ... } // 이 경우 어떻게 될까?
-
- 그렇다면 정적 객체를 배열로 만들면?!
- 해당 배열의 크기는 어떻게 정의할 것인가.
- 너무 큰 크기를 할당해두면 되려 수행 성능이 떨어진다. 이는 기존의 의미를 퇴색시키는 일.
-
새로운 객체를 반환하는 함수의 정도
- 새로운 객체를 반환하게 만들자.
const Rational operator*(const Rational &lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
- 생성 / 소멸 비용이 들어가지만 올바른 동작에 지불되는 작은 비용이다.
- 반환값 최적화(return value optimization)(RVO)의 여지
- C++ 컴파일러는 기존 코드의 수행 성능을 높이는 최적화를 적용할 수 있다.
- 그 결과로 컴파일 후 해당 반환값에 대한 생성 / 소멸 동작이 안전하게 제거될 여지가 있다.
728x90