일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- more effective c++
- 메타테이블
- 함수 객체
- 비교 함수 객체
- virtual function
- 오블완
- Smart Pointer
- implicit conversion
- operator new
- 언리얼
- reference
- 루아
- exception
- 반복자
- lua
- 암시적 변환
- 스마트 포인터
- effective stl
- 티스토리챌린지
- 상속
- Effective c++
- 참조자
- 예외
- UE4
- 다형성
- 영화 리뷰
- 영화
- c++
- 게임
- resource management class
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 46. 템플릿 클래스 안에 비멤버 함수 두기 본문
728x90
항목 46. 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자
- 선행 사항 : 우선 '모든 매개변수에 대해 암시적 타입 변환을 지원하려면 비멤버 함수밖에 방법이 없다'를 숙지하는 것이 좋다.(항목 24 참조)
Rational 클래스의 operator* 함수를 템플릿으로 만들어보자
template<typename T>
class Rational
{
public:
Rational(const T& numerator = 0, const T& denominator = 1);
const T numerator() const;
const T denominator() const;
...
};
// operator* 템플릿 비멤버 함수
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 객체 두개를 받는 operator*) 컴파일러가 명확히 안다.
- 하지만, 템플릿 버전의 경우 어떤 함수를 호출하려는지 컴파일러로서는 아는 바가 전혀 없다.
- 컴파일러는 Rational<T> 타입의 매개변수를 두 개 받는 operator* 함수로 어떻게든 인스턴스를 만들려 한다.
- 하지만, 이 인스턴스화를 진행하기 위해선 T 타입이 뭔지 추론해야 하나, 컴파일러는 이를 추론할 수 없다.
operator*에 우겨넣기 위한 컴파일러의 추론 과정
- T의 정체를 파악하기 위해 우선 operator* 호출 시에 넘겨진 인자의 모든 타입을 살핀다.
- 지금의 경우 Rational<int>(oneHalf의 타입)와 int(2의 타입) 두 종류이다.
- oneHalf의 경우
- Rational<T> 타입과 Rational<int> 타입을 대응해 내는 데 성공한다.
- 2의 경우
- operator*의 두 번째 매개변수가 Rational<T> 타입이며, 그러니 2는 Rational<int>로 암시적으로 변환될 것이다?
- 하지만 컴파일러는 이렇게 추론해내지 못한다.
- 템플릿 인자 추론 과정에서는 암시적 타입 변환이 고려되지 않으므로 Rational<T> 매개변수에 2를 대입하는 것에 실패한다!
해결 방법
- 클래스 템플릿 안에 friend 함수를 선언하여 해결한다.
- 이렇게 하면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있기 때문이다.
- 즉, 이 friend 함수는 템플릿 인자 추론 과정을 회피할 수 있다.
template<typename T>
class Rational
{
public:
...
// 템플릿 클래스 내에 friend 함수 선언
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{ ... }
- 문법 해설
- 클래스 템플릿 내부에서는 템플릿의 이름(<>를 뗀 것)을 그 템플릿 및 매개변수의 줄임말로 쓸 수 있다.
- 즉, Rational<T> 안에서는 Rational이라고만 써도 Rational<T>로 먹힌다.
- 클래스 템플릿 내부에서는 템플릿의 이름(<>를 뗀 것)을 그 템플릿 및 매개변수의 줄임말로 쓸 수 있다.
- 허나 이 코드의 경우 링크 에러가 발생한다.
링크 에러 해결
- friend 함수의 정의가 없기 때문이다.
- 클래스 외부의 operator* 템플릿이 함수 정의를 제공했으면 했지만... 당연히 자동으로 해결되진 않았다.
- 가장 간단한 해결 방법은, 함수 본문을 선언부와 붙이는 것이다.
template<typename T>
class Rational
{
public:
...
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
rhs.denominator() * rhs.denominator());
}
};
- 클래스 안에 정의된 함수는 인라인으로 선언된다.
- 클래스 바깥에서 정의된 도우미 함수만 호출하는 식으로 operator*를 구현하면 위의 암시적 인라인 선언의 영향을 최소화할 수 있을 것이다.
- "프랜드 함수는 도우미만 호출하게 만들기" 방법
- 도우미 함수를 사용한 대략적인 예제
template<typename T> class Rational;
// 도우미 함수 템플릿
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());
}
template<typename T>
class Rational
{
public:
...
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
// 프렌드 함수가 도우미 함수를 호출하게 만든다.
return doMultiply(lhs, rhs);
}
...
};
// 혼합형 곱셈 지원을 위한 템플릿 비멤버 함수
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{ ... }
- 이 경우, doMultiply 함수 템플릿은 혼합형 곱셈을 지원하지 못할테지만, 어차피 operator*가 지원해주니 문제 없다.
friend 함수의 용법
- 보통은 friend 함수를, 제한 영역(protected, private 영역)에 접근하기 위해 사용한다.
- 하지만 이 경우에는 모든 인자에 대해 타입 변환이 가능하도록 만들기 위해서 friend 함수를 선언했다.
- 공교롭게도 클래스 안에 비멤버 함수를 선언하는 유일한 방법이 friend 였을 뿐이다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 48. 템플릿 메타 프로그래밍 (0) | 2024.07.22 |
---|---|
[Effective C++] 47. 특성 정보 클래스 (0) | 2024.07.19 |
[Effective C++] 45. 멤버 함수 템플릿으로 암시적 변환 지원 (1) | 2024.07.17 |
[Effective C++] 44. 템플릿 코드 비대화 회피하기 (0) | 2024.07.16 |
[Effective C++] 43. 템플릿 부모 클래스의 인터페이스에 접근하기 (0) | 2024.07.15 |
Comments