티스토리 뷰

728x90

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


참조자를 왜 반환해도 되지 않나?
매개변수가 참조자이고, 그 참조자를 반환하는 경우에는 상관이 없다. 
하지만, 함수 내부에서 선언된 객체의 경우에는 크게 잘못 된다.

이유?
스택에 올라간 객체는 존재 할수 있는 범위(Scope)가 있다.
함수 내부에 선언(스택에 올려진)된 객체의 경우, 함수 종료 후 사라진다.
그러므로 안에 있던 객체를 참조자로 리턴하게되면 문제가 발생한다.

함수에서 객체를 반환하는 경우, 임시 객체를 생성하는 것을 피하기 위해 참조자를 반환하려고 시도하는 경우가 있다.
그러나 그것은 옳은 방법이 아니다.

참조자라는 것은 이미 존재하는 객체에 대한 별칭과 같은 것이다. 
함수에서 스택에 임시로 생성된 객체에 대해 참조자를 반환하게 되면, 함수 반환 시에 임시 객체가 사라지면서 그 참조자는 무용지물이 된다. 
그러한 참조자를 이용하면서 발생하는 문제는 예측할 수 없다.

스택에서 생성하지 않고 힙에서 생성한 객체의 참조자를 반환한다해도 여전히 문제는 남아있다.
사용자 측에서 자원을 해제해주어야 하는데, 이를 깜박하면 자원 누수가 발생한다.
또한, 한 문장에 중첩하여 호출하는 경우 아예 자원을 해제할 수 없다.

 

 

 

함수 수준에서 새로운 객체를 만드는 방법

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
32
33
34
35
36
37
38
39
40
41
//1.스택에 생성
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 & lhs, const Rational & rhs)
{
    Rational *result = new Rational (lhs.n * rhs.n, lhs.d * rhs.d);
    return result
}
//함수 내에서 new 로 생성한 객체에 대한 소멸(Delete) 의무를 
//사용자에게 넘기게 되므로 메모리 누출에 취약하다.
 
 
void main()
{
    Rational w, x, y, z ;
    W = x * y * z;        //operator*(operator*(x,y),z) 와 같다.
    // operator*의 호출이 두번 일어나는데 operator*(x,y)에서 반환되는 포인터를 얻을 방법이 없으므로, 
    // 이것에 대한 Delete 작업을 수행할 마땅한 방법이 없다.
 
 
//3.정적 객체(Static)로 생성
const Rational & operator*(const Rational & lhs, const Rational & rhs)
{
    static Rational *result;// 반활할 참조자가 가리킬 정적 객체
    Result = Modi();        //lhs 와 rhs를 곱하고 그 결과를 result에 저장합니다
    return * result;
}           
//함수를 여러번 호출하더라도 생성되는 정적영역은 처음의 것 하나 뿐이므로, 
//다시 함수를 호출하면 이전의 호출에서 반환 받는 객체는 새로운 호출 값으로 바뀌게 된다.
 
 
cs

 

 

새로운 객체를 반환해야 하는 함수의 정석

1
2
3
4
5
inline const Rational operator *(const Rational & lhs, const Rational & rhs)
{
    return Rational (lhs.n * rhs.n, lhs.d * rhs.d);
}
 
cs

 

 

결론


그러면, 생성되고 소멸되는 객체를 어떻게 최적화 시키나?
결론적으로 생성자, 소멸자 호출 비용을 감당하더라도 함수에서 객체를 반환하는 경우에 참조자를 반환해서는 안된다. 객체를 새롭게 만드는 과정이므로 어찌보면 당연히 지불해야 하는 비용인 셈이다.
그러나 반환 값 최적화 ( return value optimizaion, RVO ) 기능을 가진 컴파일러에서는 컴파일 과정에서 객체의 생성, 소멸 동작을 제거할 수도 있다. 그러므로 사용자 입장에서는 최소한의 필요 동작, 즉 새로운 객체를 반환시키는 동작만을 ( 복사나 대입 연산이 발생하지 않도록 ) 구현하는 것이 최대한의 최적화 방법이다.

이것만은 잊지 말자!

함수에서 객체를 반환하는 경우, 임시 객체를 생성하는 것을 피하기 위해 참조자를 반환하려고 시도하는 경우가 있다. 
그러나 그것은 옳은 방법이 아니다. 참조자라는것은 이미 존재하는 객체에 대한 별칭과 같은 것이다. 
객체 생성 비용은 당연히 지불해야 하는 비용이며, 컴파일러에게 최적화를 맡겨고 그 외의 비용을 최소화하자.


반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
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
글 보관함
250x250