KoreanFoodie's Study

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

Tutorials/C++ : Advanced

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

GoldGiver 2022. 10. 25. 16:32

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

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

핵심 :

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

 

다음과 같은 유리수 계산 클래스가 있다고 해 보자.

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<int> result = oneHalf * 2;

위 코드에서 result 가 동작하지 않는 이유는, 2 라는 int 타입 변수를 매개변수로 받는 과정(템플릿 인자 추론 - template argument deduction)에서는 암시적 타입 변환이 고려되지 않기 때문이다!

이런 타입 변환은 함수 호출이 진행될 때 쓰이는 것은 맞다. 하지만, 템플릿 인자 추론이 진행되는 동안에는 생성자 호출을 통한 암시적 타입 변환 자체가 고려되지 않는다.

그런데, 클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있다. 클래스 템플릿은 템플릿 인자 추론 과정에 좌우되지 않으므로(템플릿 인자 추론은 함수 템플릿에만 적용되는 과정임), T 의 정확한 정보는 Rational<T> 클래스가 인스턴스화 될 당시에 바로 알 수 있다.

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

이제 이 코드는 컴파일이 된다. oneHalf 객체가 Rational<int> 타입으로 선언되면 Rational<int> 클래스가 인스턴스로 만들어지고, 그 과정의 일부로 Rational<int> 타입의 매개변수를 받는 프렌드 함수인 operator* 도 자동으로 선언된다. 이전과 달리 지금은 함수가 선언된 것이므로(함수 템플릿이 아니라) 컴파일러는 이 호출문에 대해 암시적 변환 함수(Rational 의 비명시 호출 생성자) 를 적용할 수 있게 된다. 또한, Rational<T> 로 써도, Rational 로 써도 같은 의미이다. 다만 코드가 더 깔끔해졌을 뿐이다.

모든 인자에 대해 타입 변환이 가능하도록 만들기 위해 비멤버 함수가 필요하고, 호출 시의 상황에 맞는 함수를 자동으로 인스턴스화 하기 위해서는 그 비멤버 함수를 클래스 안에 선언해야 한다. 공교롭게도, 클래스 안에 비멤버 함수를 선언하는 유일한 방법이 '프렌드'이다(public 여부랑은 관계없음).

추가적으로, 클래스 안에 정의된 함수는 암시적으로 인라인된다. 물론, 다음과 같이 "도우미 함수" 가 호출되게 만듦으로써 인라인의 영향을 피할 수 있다.

template<typename T>
class Rational
{
public:
	friend 
	const Rational operator*(const Rational& lhs, const Rational& rhs)
	{
		// 프렌드 함수가 도우미 함수를 호출하게 만듦
		return doMultiply(lhs, rhs);
	}
};

// 도우미 함수 템플릿은 일반적으로 헤더 파일에 들어감
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
	return Rational<T>(lhs.numerator() * rhs.numerator(),
	lhs.denominator() * rhs.denominator());
}

 

Comments