- 어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체도 포함)에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 한다.
모든 매개변수에 대한 타입 변환은 비멤버 함수로
어떤 함수에서 모든 매개변수에 대한 암시적 변환을 지원하기 위해서는 그 함수를 비멤버 함수로 선언해야 한다.
유리수를 나타내는 클래스 Rational이 있다고 가정해 보자.
class Rational {
public:
Rational(int numerator = 0, int denominator = 1); // not explicit = 암시적 변환 허용
int numerator() const; // getter
int denominator() const;
private:
...
};
유리수 클래스의 편의성을 올리기 위해 정수와의 연산도 가능하게 만들고 싶다.
operator *를 클래스 내부에서 구현한다고 해보자.
class Rational {
public:
...
const Rational operator*(const Rational& rhs) const;
};
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = ontHalf * oneEighth; // OK
result = result * oneEighth; // OK
result = oneHalf * 2; // OK
result = 2 * oneHalf; // Error
유리수인 두 수의 연산은 자연스럽게 처리할 수 있다. 하지만, 다른 타입(정수)과의 연산은 문제가 생긴다.
Rational 객체를 왼쪽에서 곱할 때는 허용이 되지만, 정수를 왼쪽에서 곱할 때는 error가 발생한다.
곱셈은 기본적으로 교환법칙이 성립해야 하는데 반쪽짜리 기능을 하는 구현이 된다.
oneHalf 객체가 왼쪽에 있으면 operator* 함수를 멤버로 갖고 있는 클래스의 인스턴스이기 때문에 컴파일러가 이 함수를 호출하지만 oneHalf 객체가 오른쪽에 있다면 왼쪽에 있는 정수 2의 operator*를 찾아 호출할 것이다.
하지만, 정수 2에는 operator* 멤버 함수가 존재하지 않을 것이다.
따라서, 컴파일러는 다음과 같은 operator*의 비멤버 버전을 찾을 것이다.
result = operator*(2, oneHalf); // Error
하지만, int와 Rational을 취하는 비멤버 버전의 operator*가 없어서 컴파일이 실패할 것이다.
그렇다고 모든 숫자형 타입에 대한 operator*를 만들 수도 없다.
컴파일이 성공한 부분을 살펴보면 답을 알 수 있다.
result = OneHalf * 2; // OK
const Rational temp(2); // 2로부터 임시 Rational 객체 생성
result = oneHalf * temp; // = oneHalf.operator(temp);
분명 Rational 객체를 인자로 받아야 하는데 2를 받아도 컴파일이 성공하는 이유는 2로부터 암시적 변환이 이루어지기 때문이다.
컴파일러는 전달받은 int를 Rational 클래스 생성자에 전달하면 Rational 객체를 생성할 수 있다는 사실을 알고 있다.
컴파일러가 이렇게 동작한 것은 명시호출로 선언되지 않은 생성자가 있기 때문이다. 만약 명시호출 생성자였다면 Rational 객체가 왼쪽이든 오른쪽이든 컴파일되지 않았을 것이다.
매개변수의 암시적 변환을 허용하게 하려면 매개변수 리스트에 들어 있어야 한다.
즉, this 포인터가 가리키는 객체는 암시적 변환이 적용되지 않는다.
해결법
온전한 혼합형 수치 연산을 지원하고 싶다면 operator*를 비멤버 함수로 만들어서 컴파일러가 모든 인자에 대해 암시적 변환을 수행하도록 하면 된다.
class Rational {
...
};
const Rational operator*(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; // OK
result = 2 * oneFourth; // OK
operator*를 Rational 클래스의 프렌드 함수로 두어도 되는지 고민할 수 있다.
정답은 그렇지 않다. operator*는 Rational의 public 인터페이스만 사용해서도 구현할 수 있다.
멤버 함수의 반대는 프렌드 함수가 아리나 비멤버 함수이다.
프렌드 함수는 피할 수 있다면 피하는 것이 좋다.
멤버 함수가 아니어야한다는 것은 프렌드 함수이어야 한다와 같은 말이 아니다.