모든 매개변수에 대해 암시적 타입 변환이 되도록 만들기 위해서는 비멤버 함수밖에 방법이 없다.
template<typename T>
class Rational
{
public:
Rational(const T& numerator = 0, const T& denominator = 1)
: n(numerator), d(denominator)
{}
const T Numerator() const { return n; }
const T Denominator() const { return d; }
private:
T n, d;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, Rational<T>& rhs); //비멤버 함수
int main()
{
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2; //에러
system("Pause");
return 0;
}
현재 oneHalf * 2에서 에러가 발생한다. 비멤버 함수로 operator*를 선언했기 때문에 예전처럼(24장 참고) 암시적 타입 변환이 되어야 하는데 왜 안될까?
현재는 템플릿 클래스이기 때문에 컴파일러가 어떤 함수를 호출하려는지 알 수가 없다. 어떤 함수라 함은 Rational<int>의 operator*인지 Rational<double>의 operator*인지 알 수가 없다는 것이다.
컴파일러는 그저 Rational<T> 타입의 매개변수 두 개를 받아들이는 operator*라는 이름의 함수를 인스턴스로 만들어야 한다는 사실 뿐이다.
컴파일러가 oneHalf * 2에서 2는 int형이니 Rational<int>로 암시적 변환하고 T가 int임을 유추하지 않을까 싶지만, 템플릿 인자 추론(template argument deduction)(*주 1) 과정에서 암시적 타입 변환은 고려되지 않는다.
프렌드 함수로 선언
클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있다. 템플릿 인자 추론은 함수 템플릿에만 적용되므로 클래스 템플릿은 템플릿 인자 추론 과정에 좌우되지 않으므로 T의 정확한 정보는 Rational<T> 클래스가 인스턴스화 될 당시에 알 수 있다.
template<typename T>
class Rational
{
public:
...
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, Rational<T>& rhs)
{
return Rational<T>(lhs.Numerator() * rhs.Numerator(), lhs.Denominator() * rhs.Denominator());
}
int main()
{
Rational<int> oneHalf(1, 2);
Rational<int> result = oneHalf * 2; //여전히 실행 안됨
system("Pause");
return 0;
}
인텔리센스에서 에러는 잡혔다. 하지만 실행은 되지 않는다. 책의 말에 따르면 컴파일은 되는데 링크가 안 된다고 한다.
현재 컴파일러는 우리가 어떤 데이터(Rational<int>, Rational<double> 등)의 함수를 호출하려는지 알 수 있으나, 이 함수는 Rational안에서 선언만 되어 있지 정의까지 되어 있는 것은 아니다. 즉, 클래스 외부의 operator* 템플릿에서 함수 정의를 제공하려고 했지만, 실제로 이 함수가 쓰이지는 않는다. 클래스 내부의 함수만 쓰이기 때문에 정의까지 클래스 내부에서도 해야한다.
template<typename T>
class Rational
{
public:
...
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
};
현재 friend 함수에서 템플릿 매개변수를 사용하지 않았는데, 템플릿 클래스 내부에서는 템플릿의 이름을 그 템플릿 및 매개변수의 줄임말로 사용할 수 있다. 템플릿 클래스 내부라면 Rational과 Rational<T>가 같은 의미를 갖는다.
암시적 인라인 최소화하기
현재 템플릿 클래스 내부의 friend 함수 operator*은 클래스 내부에 있기에 암시적으로 인라인으로 선언될 것이다.
현재 코드는 Rational의 사본을 반환할 뿐이니 크게 상관 없지만, 코드가 길어져 인라인으로 인해 파일 크기가 걱정일 때 사용할 법한 방법이 있다. 구현 코드를 클래스 외부의 비멤버 함수로 빼고 프렌드 함수는 그 비멤버 함수를 호출하게 한다.
template<typename T>
class Rational
{
public:
...
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Multiply(lhs, rhs); //비멤버 함수 호출
}
};
template<typename T> //프렌드 함수의 내부 구현 코드
const Rational<T> Multiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
이것만은 잊지 말자
모든 매개변수에 대해 암시적 타입 변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런 함수는 클래스 템플릿 안에 프렌드 함수로서 정의하자.
(*주 1) 템플릿 인자 추론(template argument deduction)
컴파일러가 함수 인자를 보고 템플릿의 타입을 결정하는 것
template<typename T>
void Func(T a);
int main()
{
int a = 0;
double b = a;
Func(a); //T : int
Func(b); //T : double
system("Pause");
return 0;
}
'서적 정리 > Effective C++' 카테고리의 다른 글
50.new 및 delete를 언제 바꿔야 좋은 소리를 들을지 파악해 두자 (0) | 2022.01.01 |
---|---|
49.new 처리자의 동작 원리를 제대로 이해하자 (0) | 2021.12.29 |
48.템플릿 메타프로그래밍, 하지 않겠는가? (0) | 2021.12.26 |
47.타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 (0) | 2021.12.25 |
45."호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방! (0) | 2021.12.24 |
44.매개변수에 독립적인 코드는 템플릿으로부터 분리시키자 (0) | 2021.12.24 |
43.템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 (0) | 2021.12.23 |
42.typename의 두 가지 의미를 제대로 파악하자 (0) | 2021.12.23 |
댓글