스토리텔링 개발자

[More Effective C++] 22. 대입 형태 연산자 선호하기 본문

개발/More Effective C++

[More Effective C++] 22. 대입 형태 연산자 선호하기

김디트 2024. 8. 30. 14:27
728x90

항목 22 : 단독 연산자(op) 대신에 =이 붙은 연산자(op=)를 사용하는 것이 좋을 때가 있다.

 

 

 

사용자가 기대하는 연산자 동작
// 아래의 연산자가 동작한다면,
x = x + y;
x = x - y;

// 아래의 연산자도 동작한다고 기대하게 된다.
x += y;
x -= y;
  • 하지만 사용자 정의 타입이라면 위의 두 형태의 코드가 지원되리라는 보장은 전혀 없다.
    • C++에서는 operator+, operatr=, operator+= 사이에 아무 관계가 없다.
    • 즉, 사용자의 기대를 충족시키려면 구현하는 수밖에 없다.

 

 

 

대입 형태(operator+=)와 단독 형태(operator+) 관계 맺기
  • 대입 형태를 사용해서 단독 형태를 구현하는 것도 괜찮은 방법이다.
class Rational
{
public:
    ...
    Rational& operator+=(const Rational& rhs);
    Rational& operator-=(const Rational& rhs);
};

// operator+=를 사용해서 구현
// 반환값이 const인 이유(항목 6 참조)
const Rational operator+(const Rational& lhs,
                         const Rational& rhs)
{
    return Rational(lhs) += rhs;
}

// operator-=를 사용해서 구현
// 반환값이 const인 이유(항목 6 참조)
const Rational operator-(const Rational& lhs,
                         const Rational& rhs)
{
    return Rational(lhs) -= rhs;
}
  • 해당 형태의 장점
    • 대입 형태만 유지보수 해주면 된다.
    • 위의 예제처럼, 대입 형태의 연산자가 public이라면?
      • 단독 형태의 연산자를 클래스의 프렌드(friend)로 만들지 않아도 된다.

 

 

 

템플릿을 써서 단독 형태 연산자를 자동 생성하기
  • 단독 형태 연산자를 모두 전역 유효 범위(global scope)에 두어도 괜찮다면 선택 가능한 방법.
  • 클래스 안에 단독 형태의 연산자 함수를 구현할 필요성을 없앨 수 있다.
template<typename T>
cosnt T operator+(const T& lhs, const T& rhs)
{
    return T(lhs) += rhs;
}

template<typename T>
cosnt T operator-(const T& lhs, const T& rhs)
{
    return T(lhs) -= rhs;
}

// 위 템플릿과 타입 T에 대한 대입 형태 연산자만 준비되어 있다면
// 단독 형태 연산자는 자동으로 만들어진다.

 

 

 

대입 형태와 단독 형태 연산자의 효율에 대한 팁

 

1. 대입 형태 연산자가 보통 더 효율적이다.

  • 단독 형태 연산자는 새 객체를 반환하도록 구현된다.
  • 즉, 임시 객체를 생성하고 소멸하는 비용이 소모된다.(항목 19, 20 참조)

2. 대입 형태와 단독 형태 연산자를 모두 제공하면, 사용자는 효율 / 편리성 둘을 가늠해서 취사선택할 수 있다.

Rational a, b, c, d, result;

// 방법 1
// operator+당 임시 객체가 발생한다.(임시 객체 3개)
// 허나 작성과 디버깅, 유지보수에 이점이 있다.
result = a + b + c + d;


// 방법 2
// 임시 객체가 발생하지 않는다.
// 하지만 디버깅과 유지보수에는 적합하지 않다.
rasult = a;
result += b;
result += c;
result += d;
 

3. 단독 형태 연산자를 구현할 때 이름 없는 임시 객체가 더 효율적임을 인지하고 있도록 하자.

// 방법 1
template<typename T>
const T operator+(const T& lhs, const T& rhs)
{
    return T(lhs) += rhs; // 이름 없는 임시 객체(lhs의 복사본)를 반환한다.
}

// 방법 2
template<typename T>
const T operator+(const T& lhs, const T& rhs)
{
    T result(lhs); // result에 lhs를 복사한다.
    return result += rhs; // result를 반환한다.
}
  • 동일하게 동작할 것 같아보이나 두 번째 방법은 약간 더 비효율적이다.
    • 반환되는 객체가 이름 있는 객체라는 사실은, 컴파일러에 따라 반환값 최적화(항목 20 참조)가 동작하지 않는다는 것을 의미한다.(최소한 구 버전 컴파일러는 그렇다.)
    • 하지만 첫 번째 방법은 항상 RVO(반환값 최적화, Return Value Optimization)가 항상 먹히는 코드이다.
728x90
Comments