본문 바로가기
책 정리/Effective C++ 3rd

항목 46. 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자.

by ocean20 2020. 3. 25.

항목 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() );
};

 이제 컴파일, 링크, 실행 모두 된다.

 

여기서 한단계 더 나아가 보자.

바로 위 코드의 문제점은 클래스 안에 정의된 함수의 본문은 인라인으로 선언된다. 따라서 클래스 바깥에 본문을 분리하여 호출하면 암시적 인라인 선언의 영향을 최소화 할 수 있다.


이것만은 잊지 말자!

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

 

 

댓글