728x90
반응형

모든 매개변수에 대해 암시적 타입 변환이 되도록 만들기 위해서는 비멤버 함수를 사용해야 한다.

템플릿화 한 Rational 클래스

template<typename T>
class Rational
{
public:
  Rational(const T& num = 0, const T& deno = 1) 
   : numerator(num), denominator(deno) {}

  const T Numerator() const
  {
    return numerator;
  }

  const T Denominator() const
  {
    return denominator;
  }

private:
  T numerator;
  T denominator;
};

template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
    // ...
}

Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2; // 컴파일 에러!

위의 코드는 컴파일 에러가 나는데, 템플릿의 경우 어떤 함수를 호출하려는지 컴파일러는 모르기 때문이다.

컴파일러가 아는 것은 Rational <T> 타입의 매개변수를 두 개 받아들이는 operator*라는 이름의 함수를 인스턴스로 만들어야 한다는 점이다. 하지만 이 인스턴스화를 제대로 하려면 T가 무엇인지 알아야 하는데, 컴파일러는 이 것을 알지 못한다.

위의 코드에서 일어나는 일은 operator* 호출 시에 넘겨진 인자의 모든 타입을 살피고, 이들을 각자 살핀다.

컴파일러는 operator*에 선언된 매개변수들은 Rational <T>이고, 첫 번째 인자 oneHalf는 Rational <int> 이므로 T는 int임을 알 수 있으나 두 번째 매개변수는 int타입이다. 그래서 두 번째 매개변수의 경우 T의 타입을 유추하기가 쉽지 않은데 '만약 컴파일러가 생성자를 써서 2를 Rational <int>로 변환하고 T가 int임을 알 수 있지 않을까?'라는 생각을 해볼 수 있지만 템플릿 인자 추론과정에서는 암시적 타입 변환은 고려되지 않기 때문에 불가능하다.

 

컴파일 문제 해결

클래스 템플릿 안에 프랜드 함수를 넣어 두면 된다.

이렇게 되면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있다는 사실을 이용하는 것이다.

클래스 템플릿은 템플릿 인자 추론 과정에 좌우되지 않으므로(템플릿 인자 추론은 함수 템플릿에서만 적용된다.), T의 정확한 정보는 Rational <T> 클래스가 인스턴스화 될 당시에 바로 알 수 있다.

template<typename T>
class Rational
{
public:
  // operator* 함수 선언
  friend const Rational operator*(const Rational<T>& lhs, const Rational<T>& rhs); 
  // 클래스 템플릿 내부에서는 템플릿의 이름(<>뗀것)을 그 템플릿 및 매개변수의 줄임말로 쓸 수 있다.
  // Rational<T> == Rational
  // friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs); // 위와 동일한 함수이다. 
};

// operator* 함수 정의
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) 
{
  // ...
}

하지만 이 경우 선언만 되어있고 정의는 되어 있지 않아 컴파일은 되지만 링크가 되지 않는다.

 

링크 문제 해결

operator* 함수의 본문을 선언부와 붙인다.

template<typename T>
class Rational
{
public:
  friend const Rational operator*(const Rational<T>& lhs, const Rational<T>& rhs)
  {
    return Rational(lhs.numerator() * rhs.numerator(),
                    lhs.denominator() * rhs.denominator());
  }
};

이렇게 되면 컴파일, 링크, 실행 모두 가능하다.

operator*는 간단하니 암시적으로 인라인으로 선언했지만, 클래스 바깥에서 정의된 도우미 함수만 호출하는 식으로 구현도 가능하다.

만약 꽤 복잡한 함수라면 프랜드 함수는 도우미 함수만 호출하게 만드는 것도 좋은 방법일 것이다.

 

요약

  • 모든 매개변수에 대해 암시적 타입 변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런 함수는 클래스 템플릿 안에 프랜드 함수로서 정의하자.
728x90
반응형