[Effective C++] 항목24 : 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자

Jangmanbo·2023년 5월 17일
0

Effective C++

목록 보기
24/33

암시적 타입 변환

// 유리수 클래스
class Rational {
public:
	// explicit을 붙이지 않았으므로, int->Rational 암시적 변환 허용
	Rational(int numerator = 0, int denominator = 1);
    
    // 분자/분모 접근 함수 (항목22 참고)
    int numerator() const;
    int denominator() const;
    
    const Rational operator*(const Rational& rhs) const;
    
private:
	...
};

일반적으로 클래스에서 암시적 타입 변환을 지원하지 않는 것이 좋다.
그러나 이 규칙에는 예외가 있는데, 가장 흔한 예외는 숫자 타입이다.

C++에서 기본으로 제공하는 int->double처럼 정수->유리수 암시적 변환을 허용하기 위해 explicit을 붙이지 않았다.
수치연산도 기본적으로 지원하고자 곱셈 연산을 지원하는 operator* 함수를 만들었다.


혼합형 수치 연산

이렇게 만든 Rational 클래스는 혼합형 수치 연산(ex. int * Rational)을 지원할 것 같지만 실제론 그렇지 않다.

result = oneHalf * 2;	// Success!	result = oneHalf.operator*(2);와 동일
result = 2 * oneHalf;	// Error!	result = 2.operator*(oneHalf);와 동일

result = oneHalf * 2;에서 oneHalfoperator* 함수를 멤버로 갖고 있는 클래스 인스턴스이므로 컴파일러가 operator*를 호출한다.

const Rational temp(2);
result = oneHalf * temp

이때 operator* 함수가 Rational 객체를 요구하므로 intRational 생성자의 파라미터로 넘겨 임시 Rational 객체를 생성한다. (암시적 타입 변환)
물론 Rational생성자가 명시 호출(explicit) 생성자였다면 컴파일 에러가 발생한다.


그러나 `result = 2 * oneHalf;`에서 정수 2는 `Rational` 클래스도 아니므로 `operator*` 멤버 함수도 없다. 또한 컴파일러는 비멤버 버전의 `operator*`도 찾지 못했으므로 컴파일 에러가 발생한다.

해결법

result = oneHalf * 2;result = 2 * oneHalf;이 일관성 있게 동작하면서 혼합형 수치 연산도 제대로 동작하려면?

비멤버 함수로 만들어서 컴파일러가 모든 인자에 대해 암시적 타입 변환을 수행하도록 하자!

class Rational {
	...		// opeartor* 없음
};

// 비멤버 함수
const Rational opeartor*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.numerator() * rhs.numerator(),
    				lhs.denominator() * rhs.denominator();
}

Rational oneFourth(1, 4);
Rational result;

result = oneFourth * 2;		// Success!
result = 2 * oneFourth;		// Success!

멤버 함수의 반대는 프렌드 함수가 아니라 비멤버 함수!
보다시피 비멤버 함수 operator*Rational 클래스의 public 인터페이스만을 사용하여 구현하였다. 이렇게 충분히 public 멤버만으로 구현이 가능하다면 프렌드 함수가 아닌 비멤버 함수로 구현하는 것이 좋다.

일반적으로 프렌드는 여러 골칫거리를 만들기 때문이다!


정리
어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체 포함)에 대해 타입 변환이 필요하다면, 그 함수는 비멤버여야 한다.

0개의 댓글