항목 46. 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자.
항목 24를 보고오자. https://browoo.tistory.com/201?category=740173
아래 코드는 항목 24의 코드를 템플릿화한 코드이다.
template<typename T>
class Rational{
public :
Rational(const T& numerator = 0, const T& denominator = 1);
const T numerator() const;
const T denominator() const;
…
};
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
{ … }
Rational<int> oneHalf(1, 2); // Rational이 템플릿이라는 점만 빼면 항목24의 예제와 동일하다.
Rational<int> result = oneHalf * 2; // Error ! 왜 ?
* 풀이
- [항목 24] 에서, 컴파일러는 호출하려고 하는 함수가 무엇인지 알고 있다.
- 하지만, 현 상황에서 컴파일러가 확실히 아는 것은, Rational<T>타입의 매개변수 두 개를 받아들이는 operator *라는 함수를, 자신이 어떻게든 인스턴스화 해야 된다는 것이다.
- 제대로된 인스턴스화를 위해선 "T가 무엇인지?"에 대한 수수께끼를 먼저 풀어야 한다.
- 마지막 코드를 보면 oneHalf는 첫번째 매개변수로서, Rational<int> 타입이다. 하지만 두번째 매개변수인 "2"를 확인한 컴파일러는 이 "2"의 T타입이 무엇인지 판단할 수 없다.
- 혹시 Rational의 explicit 키워드를 사용하지 않은 생성자를 보고 암시적변환이 가능하지 않을까?라고 생각할수도 있다.
- 하지만 템플릿 인자추론과정에서는 앞서말한 암시적 타입변환을 고려하지 않는다.
* 해결방법
- 템플릿 인자추론은 템플릿안에 프렌드 함수를 넣어 operator *를 프렌드 함수로 선언하여 해결한다. 템플릿 인자 추론은 함수템플릿에만 좌우되며, 클래스템플릿에는 영향을 주지 않는다. 따라서, T의 정확한 정보는 Rational<T>클래스가 인스턴스화 될때 알 수 있다.
template<typename T>
class Rational {
public :
...
friend
const Rational operator*(const Rational& lhs, const Rational& rhs); // operator*함수를 선언
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) // operator* 를 정의
{ ... }
이제 우리는 operator* 가 호출이 컴파일 되는 코드를 볼 수 있다. oneHalf 객체가 Rational<int> 객체로 선언되면서 Rational<int> 클래스가 인스턴스로 만들어지고, 이 과정에서 friend operator* 자동 선언되기 때문이다.
[참고사항]
- 클래스 템플릿 내부에서는 템플릿의 이름(<>뗀 것)을 그 템플릿 및 매개변수의 줄임말로 쓸 수 있다. 그러니깐 Rational<T>안에서 Rational 이라고만 써도 정상작동한다.
template<typename T>
class Rational {
public :
...
friend
const Rational operator*(const RAtional& lhs, const Rational& rhs);
...
};
하지만, 링크단계에서 다시 에러가 발생한다.
이 함수가 Rational안에서 선언만 되어있고 정의 되어 있지 않기 때문이다.
*해결방법 : operator* 함수 본문을 선언부와 붙인다.
template<typename T>
class Rational {
public :
...
friend
const Rational operator*(const Rational& lhs, const Rational& rhs); // operator*함수를 선언
{
return Rational( lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator() );
};
이제 컴파일, 링크, 실행 모두 된다.
여기서 한단계 더 나아가 보자.
바로 위 코드의 문제점은 클래스 안에 정의된 함수의 본문은 인라인으로 선언된다. 따라서 클래스 바깥에 본문을 분리하여 호출하면 암시적 인라인 선언의 영향을 최소화 할 수 있다.
이것만은 잊지 말자!
- 모든 매개변수에 대해 암시적 타입 변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런 함수는 클래스 템플릿 안에 프렌드 함수로서 정의합시다