함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자.


유리수를 나타내는 클래스가 있고 이 클래스에는

두 유리수를 곱하는 멤버 함수가 선언되어 있다.


Rational {

private:

int n ,d;

public:

Rational(int _n, int _d);

...

const Rational operator*(const Rational& lhs, const Rational& rhs);

};




Rational operator*(..); 위 함수에서는

참조자가 아닌 객체를 반환하고 있다.

이 함수가 참조자를 반환하도록 만들어졌다면,

이 함수가 반환하는 참조자는 반드시 이미 존재하는

Rational 객체의 참조자여야 한다.


참조자를 반환한다면 객체의 생성과 소멸에 드는

비용을 조금이라도 줄여지지 않을까?

그렇다면 참조자를 반환해보자.


const Rational& operator*(const Rational& lhs, const Rational& rhs){

Rational result (lhs.n * hrs.n, lhs.d * rhs.d );

return result;

}




참조자를 반환하려면 그 객체가 존재해야 한다고 말했다.

따라서 어차피 함수 안에서 객체가 하나 만들어진다.

즉, 객체의 생성과 소멸에 드는 비용을 지불한다는 말이다.


이보다 더 큰 문제는 result는 지역 객체이다.

함수가 끝나면 이 객체는 소멸된다.

따라서 이 함수는 소멸된 객체의 참조자를

리턴하고 있는 것이다. 이 참조자가

가리키고 있는 놈은 존재하지 않는다.




그렇다면 스택이 아닌 힙에 생성하고 참조자를 반환하면 어떤가?

const Rational& operator*(const Rational% lhs, const Rational& rhs){

Rational *result = new Rational (lhs.n * hrs.n, lhs.d * rhs.d);

return *result;

}


여전히 생성자가 호출되어야한다.

그리고 new로 할당한 메모리를 누가 해제를 해주는가?

operator* 로부터 반환되는 참조자 뒤에 숨겨진

포인터에 대해서는 사용자가 어떻게 접근할 방법이 없다.

즉, 자원누출이 심각한 코드가 된다.




스택에 할당하든 힙에 할당하든 생성자를 한번은 호출한다.

그렇다면 정적 객체로 함수 안에 정의해 놓고 참조자를 반환해보자.

const Rational& operator*(const Rational% lhs, const Rational& rhs){

static Rational result;

result = ...;

return result;

}




operator==가 선언되었고 a,b,c,d 모두 Rational 객체

if((a*b) == (c*d)); 

위의 표현식은 항상 값이 true이다.


왜? operator*은 정적 객체의 참조자를 반환한다.

둘의 값이 같지 않을 수가 없다.


이쯤되면 정적 배열을 써보는 것은 어떨까?

함수가 불릴 때마다 다른 객체를 반환해야 하기 때문에

n번의 호출이 있다면 n개의 배열이 있어야 한다.

비록 한번이지만 생성자가 n번 호출되고 소멸자가 n번 호출된다.

그리고 객체에 값을 어떻게 넣을 것인가?




그냥 새로운 객체를 반환하도록 하자!

inline const Rational operator*(const Rational& lhs, const Rational& rhs){

Rational result (lhs.n * hrs.n, lhs.d * rhs.d );

return result;

}




이 코드에도 반환 값을 생성하고 소멸시키는 비용이 들지만

올바른 동작에 지불되는 작은 비용이다. 또한 몇몇 조건하에서는

최적화 메커니즘에 의해 operator*의 반환 값에 대한 생성과

소멸 동작이 안전하게 제거 될 수 있다. (반환 값 최적화 : RVO)


POINT!!

값에 의한 반환이 최선이라면 그냥 그렇게 하자.

+ Recent posts