Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
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
Archives
Today
Total
관리 메뉴

로또

24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자. 본문

책/Effective C++

24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자.

아롱로또 2023. 11. 21. 14:03

다루는 내용

비멤버 함수를 사용하여 모든 매개변수에 대한 암시적 형 변환을 지원하는 방법을 알아보자.

 

암시적 형 변환을 지원하는 방법

클래스에서 암시적 타입을 지원하는 것은 일반적으로 나쁜 생각이지만, 숫자 타입을 만들 때 만큼은 예외이다.

예를 들어, 유리수를 나타내는 클래스를 만들고 있다면 정수에서 유리수로의 암시적 변환은 허용하자고 판단하더라도 문제가 없을 것이다. 기본제공 타입마저도 int -> double 의 암시적 변환을 지원하고 있다.

class Rational{
public:
   // non - explicit한 Rational 클래스 생성자
   Rational(int n = 0, int d = 1): numerator(n), denominator(d){}
   
   int GetNumerator() const{ return numerator; }
   int GetDenominator() const{ return denominator; }
   
private:
   int numerator;
   int denominator;
};

유리수를 나타내는 클래스 Rational은 곱셈 연산을 멤버 함수로 두거나 비멤버함수, 또는 프렌드 함수로 둘 수 있다.

 

1. 멤버 함수로 두는 경우

class Rational{
public:
   // non - explicit한 Rational 클래스 생성자
   Rational(int n = 0, int d = 1): numerator(n), denominator(d){}
   // 곱셈 연산자
   const Rational operator*(const Rational& rhs) const{
      return Rational(numerator * rhs.GetNumerator(),
         denominator * rhs.GetDenominator());
   }
   // Getter
   int GetNumerator() const{ return numerator; }
   int GetDenominator() const{ return denominator; }
  
private:
   int numerator;
   int denominator;
};

int main(){
   Rational one_two(1, 2);
   Rational one_three(1, 3);
   Rational result = one_two * one_three;
   return 0;
}

위 코드는 유리수 간 계산만 다루고 있어 문제없이 컴파일, 실행될 것이다.

이제, 다른 숫자 타입(int)과의 연산을 다루어보자.

int main(){
   Rational one_two(1, 2);
   Rational result = one_two * 2; // Good
   Rational result2 = 2 * one_two; // Error
   return 0;
}

one_two * 2는 문제없다. operator*가 rhs값으로 Rational 객체를 원하고 있지만, Rational 클래스는 암시적 변환을 허용하므로 2를 인자로 받아 Rational 클래스의 객체를 생성할 수 있기 때문이다.

물론 생성자를 explicit으로 두어 암시적 변환을 허용하지 않을 경우, 다음과 같이 컴파일 에러가 발생할 것이다.

생성자를 explicit으로 두었을 때 발생하는 컴파일 오류

 

그 반대인 2 * one_two는 컴파일 에러가 발생한다. 

컴파일 에러

 

보다 편하게 이해하기 위해, 다음과 같이 호출 형태를 바꾸어보자. 위 코드와 문법적으로 동일한 의미이다.

Rational result = one_two.operator*(2);  // Good
Rational result2 = 2.operator*(one_two); // Error

첫번째 줄의 경우, one_two는 operator* 함수를 멤버로 갖고 있는 Rational 클래스의 객체이기 때문에, 컴파일러는 이 함수를 호출한다.

두번째 줄의 경우, 정수 2는 클래스같은 것이 연관되어 있지 않으므로 operator* 멤버 함수가 존재하지 않는다.

 

컴파일러는 대신 아래처럼 호출할 수 있는 비멤버 버전의 operator*(namespace 혹은 전역 유효범위에 있는 operator*)도 찾아본다.

하지만 이는 존재하지 않는 함수이므로 컴파일 에러를 발생시키는 것이다.

Rational result2 = operator*(2, one_two); // Error

 

때문에, 멤버 함수로는 모든 매개변수에 대해 암시적 형 변환을 적용할 수 없다.

 

2. 비멤버 함수로 두는 경우

매개변수에 암시적 형 변환이 적용되기 위해서는 매개변수 리스트(...)에 들어있어야만 한다. 때문에 호출되는 멤버 함수를 갖고 있는(this가 가리키는) 객체에 해당하는 매개변수에는 암시적 형 변환이 적용되지 않는다. 이 때문에 첫번째 문장은 컴파일이 되고, 두번째 문장은 컴파일되지 않는 것이다.

우리는 operator*를 비멤버 함수로 만들어 컴파일러가 모든 인자에 대해 암시적 형 변환을 수행하도록 만들어야 한다.

class Rational{ ... };

// 비멤버함수 operator*
const Rational operator*(const Rational& lhs, const Rational& rhs){
   return Rational(lhs.GetNumerator() * rhs.GetNumerator(),
      lhs.GetDenominator() * rhs.GetDenominator());
}

int main(){
   Rational one_two(1, 2);
   Rational result = one_two * 2; // Good
   Rational result2 = 2 * one_two; // Good
   return 0;
}

 

3. 프렌드 함수로 두는 경우

operator* 함수는 Rational 클래스의 friend 함수로 두어도 될까?

그렇지 않다. operator*는 Rational의 private 영역에 접근할 필요없이, 온전히 public 영역만을 사용하여 구현할 수 있기 때문이다.

멤버 함수의 반대는 프렌드 함수가 아니라 비멤버 함수이다. friend 함수는 피할 수 있으면 피하도록 하자.

 

이것만은 잊지 말자!

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