스토리텔링 개발자

[Effective C++] 21. 참조자를 리턴하면 안되는 상황 본문

개발/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은 언제 생성된 것을 리턴할 것인가?

 

 

 

참조자 반환을 위한 여러 가지 시도
  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;
      };
    • 여전히 생성자를 한번 호출한다.
    • 메모리 누수의 문제가 있다. 이 힙 생성에 대한 뒷처리(delete를 짝지어주기)가 힘들어지기 때문이다.
      • Rational w, x, y, z;
        
        w = x * y * z; // delete를 어떻게 짝지어줄 것인가?
  3. 함수 내에 정적 객체를 정의하는 방법
    • 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
Comments