로또
24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자. 본문
다루는 내용
비멤버 함수를 사용하여 모든 매개변수에 대한 암시적 형 변환을 지원하는 방법을 알아보자.
암시적 형 변환을 지원하는 방법
클래스에서 암시적 타입을 지원하는 것은 일반적으로 나쁜 생각이지만, 숫자 타입을 만들 때 만큼은 예외이다.
예를 들어, 유리수를 나타내는 클래스를 만들고 있다면 정수에서 유리수로의 암시적 변환은 허용하자고 판단하더라도 문제가 없을 것이다. 기본제공 타입마저도 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으로 두어 암시적 변환을 허용하지 않을 경우, 다음과 같이 컴파일 에러가 발생할 것이다.

그 반대인 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 포인터가 가리키는 객체도 포함해서)에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버 함수여야 한다.
'책 > Effective C++' 카테고리의 다른 글
| 26. 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자. (1) | 2023.11.24 |
|---|---|
| 25. 예외를 던지지 않는 swap에 대한 지원도 생각해보자. (0) | 2023.11.23 |
| 23. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자. (1) | 2023.11.20 |
| 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자. (0) | 2023.11.19 |
| 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자. (0) | 2023.11.19 |