Item 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자
class Rational {
public:
Rational(int numerator = 0,
int denominator = 1);
...
private:
int n, d;
friend
const Rational
operator*(const Rational& lsh,
const Rational& rhs);
};
1. operator* 함수를 아래와 같이 하면 안되는 이유는?
const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
지역변수에 대한 참조자를 리턴하고 있다. 이 함수를 호출한 곳에서 이 참조자는 더 이상 유효하지 않다.
그리고 생성자가 한 번 호출되고 있다.
2. 아래와 같이 변경하면 어떤가?
const Rational& operator*(const Rational& lsh,
const Rational& rhs)
{
Ratonal *result = new Rational(lhs.n * rhs.n,
lhs.d * lsh.d);
return *result;
}
생성자가 한 번 호출되고 있다.
new로 만든 객체를 delete 할 수 없는 경우가 생긴다.
Rational w, x, y, z;
w = x * y * z; // operator*(operator*(x, y), z) 와 같다.
곱하기가 두 번 사용되었기 때문에 operator* 함수도 두 번 호출되어야 한다. 하지만 반환된 객체에 사용자가 직접 접근할 수 없기 때문에 delete 할 수도 없다.
- 위 예제에서 모두 최소한 한 번은 생성자가 호출되어야 한다. (힙에서 객체를 만들든 스택에서 만들든 생관없이) 그런데 만약 정적객체를 만들어 두고 이것의 참조자를 반환하면 어떤가? 아래처럼.
const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
static Rational result;
result = ...; // 곱셈 결과를 여기에 저장
return result;
}
문제점
- 쓰레드 안전성에 문제가 있다.
- 함수의 리턴 값이 같은 값을 결과를 보게 된다.
bool operator==(const Rational& lhs,
const Rational& rhs);
Rational a, b, c, d;
...
if ((a*b) == (c*d)) {
두 유리수 쌍의 곱이 서로 같으면 적절히 처리
} else {
다르면 적절한 처리
}
위 코드에서 ((a*b) == (c*d)) 부분은 항상 true이다.
4. 이 경우 정적객체가 하나가 아닌 배열로 잡아서 쓰면 되나?
배열의 크기를 어떻게 정해야 하나? 크게 ? 작게? 이건 최적화 작업이 아니다.
혹시 vector를 사용하면 된다는 생각도 안된다. 별로 효율적이지않다.
5. 해결 방법은 뭔가?
방법
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * lhs.d);
}
이 코드에서도 생성과 소멸에 비용이 들지 않나?
하지만 이 경우는 정상적인 동작을 위한 비용이다.(다른 경우도 이 정도는 다 든다)
- C++ 컴파일러의 최적화 작업에 의해 위 코드의 경우 RVO(Return Value Optimization)가 적용된다. 즉 리턴되는 값을 위한 객체가 추가적으로 생성되지 않는다.
정리
- 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그 객체가 두 개 이상 필요해질 가능성이 있다면 절대로 하면 안된다.