일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 부분복사
- RAII
- 멤버함수템플릿
- operator=
- 암시적인터페이스
- EffectiveC++
- 도우미함수
- 템플릿
- 제네릭프로그래밍
- uniqueptr
- 교차dll문제
- Directx9
- private상속
- RB트리
- 상수객체참조
- 일반화복사생성자
- 스택풀림
- 가상기본클래스
- 정점버퍼
- 해골책
- 게임프로그래밍
- sharedptr
- effectivec++.
- most vexing parse
- 이른진단
- 암시적변환
- fvf
- rcsp
- c++
- 복사함수
- Today
- Total
성공할 게임개발자
[Effective c++] [7. 템플릿과 제네릭 프로그래밍] 46. 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿안에 정의해 두자 본문
[Effective c++] [7. 템플릿과 제네릭 프로그래밍] 46. 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿안에 정의해 두자
fn000 2025. 6. 27. 12:29템플릿 인자 추론 과정 ( template argument deduction )에서는 암시적 타입변환이 코려되지 않는다
항목 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);
// 항목 24에서는 비멤버 비프렌드 함수로 암시적형변환이 가능했지만 템플릿은 지원하지 않는다.
Rational<int> result = oneHalf * 2; // ERROR
혼합형 수치연산이 필요하기 때문에 컴파일 되어야하지만 마지막 줄에서 오류가 난다. 항목 24에서는 우리가 호출하려고 하는 함수가 무엇인지를 컴파일러가 알고있지만 지금 경우는 어떤 함수를 호출하는지 컴파일러가 모른다.
나의 해석
즉,
원칙적인 이유는 onehalf * 2 를 실행할 때 const Rational<T> operator* 가 정의 되어있기 때문에 이걸 사용하는데, oneHalf는 이미 타입이 Rational<int>로 확정이 나서 const Rational<T> operator* 의 첫번째 인자로 받는데 타입이 딱 맞기 때문에 패스
하지만 2는 다르다. 2가 int 형이니 Rational<int>로 암시적 형변환되지 않을까 생각하는데, 여기서 비멤버 템플릿 함수에 대해 암시적 형변환이 자동으로 적용되지 않기 때문에 템플릿 인자 추론이 실패한다. 템플릿 함수는 인자 타입이 정확히 맞아야하고 템플릿 인자 추론을 하지 않는다.
책의 해석
컴파일러는 operator*라는 이름의 템플릿으로부터 인스턴스화할 함수를 결정하기 위해 온갖 계산을 동원할 뿐(나는 다른건 안보이고 템플릿을 사용하여 이걸 실행시켜야한다). 하지만 이 인스턴스화를 제대로 하려면 T 가 무엇인가에 대한 질문을 풀어야한다. 하지만 컴파일러는 이걸 해결할 능력이 없다( 비멤버 함수니까 T를 알수가 없다)
따라서 T의 정체를 파악하기 위해 컴파일러는 operator* 호출 시에 넘겨진 인자의 모든 타입을 살핀다. 하나는 Rational<int>니까 오케이 나머지는 int인데 여기서 템플릿 인자 추론과정에서 암시적 타입변환이 절대로 고려되지 않는다.
해결방법
클래스 템플릿 안에 프렌드 함수를 넣어두면 함수 템플릿으로서의 성격을 주지 않고 특정한 함수 하나를 나타낼 수 있다. 클래스 템플릿은 템플릿 인자 추론과정에 좌우되지 않는다. T의 정확한 정보는 Rational<T> 클래스가 인스턴스화될 당시에 바로 알 수 있다. 그러니까 쉽게 말하면, 클래스 템플릿 Rational<T>가 호출될 때 기생하여 타입만 빨아먹는 느낌
template<typename T>
class Rational
{
public:
...
friend
// 여기서 operator* 에 대해 타입만 빨아 먹는다
const Rational operator*(const Rational& lhs, const Rational& rhs);
};
// 이제 이 비멤버 함수에서 T타입이 뭔지 추론가능하다
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{ ... }
따라서 컴파일러는 int에게 뿐만 아니라 이 호출문에 대해 암시적 변환 함수를 적용할 수 있게 된다.
참고) 템플릿 클래스 안에서 <>부분을 제외하고 Rational만 써도 문제되지 않는다.
해결한줄 알았지? 또 하나의 문제점 링크가 안된다.
이유 : 이 함수는 Rational 안에서 선언만 되어있지, 거기에서 정의까진 되있지 않기 때문. 즉, friend에서 선언한 함수와 함수 밖의 operator*는 다른 함수로 취급된다. 클래스 안의 friend 함수는 템플릿이 아닌 특정한 Rational<T>에 대한 일반함수로 정의되기를 기대하고 있는데, 밖의 operator* 함수는 템플릿 함수이므로 두 함수는 연결되지 않는다.
해결방법
friend함수 정의를 클래스 내부에 놓는다.
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());
}
};
모든 인자에 대해 타입 변환이 가능하도록 만들기 위해 비멤버 함수가 필요하고, ( 항목 24 )
호출 시의 상황에 맞는 함수를 자동으로 인스턴스화하기 위해서는 비멤버 함수를 클래스 안에 선언해야한다.
그 방법이 유일하게 friend 함수로 선언하는 것 이였던 것
암시적 인라인 영향 최소화
암시적 인라인의 리스크 : 컴파일러가 스스로 판단해서 어떤 함수를 인라인할지 선택하는게 가장 좋은데, 위의 경우처럼 강제적으로 복잡할 수도 있는 상황에 강제적으로 인라인을 써야하는 상황이라면 "프렌드 함수는 서포터만 호출하게 만들기" 방법을 쓸 수 도 있다.
서포터 함수 템플릿 doMultiply
template<typename T> class Rational;
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);
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> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational<T>(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
doMultiply는 템플릿함수로서 혼합형 곱셈을 지원하지 못하지만( 이 글 처음에 나왔던 operator*의 이유로) 지원못해도된다.
왜냐하면 이 함수는 operator* 내부에서 호출되며, 이 시점엔 이미 T의 타입이 명확히 정해져 있다. 즉, doMultiply는 특정 타입 전용 도우미로 사용되는 것으로, 혼합형 지원이 필요 없는 안전한 상황에서 쓰이므로 문제되지 않는다.
중요포인트!
모든 매개변수에 대해 암시적 타입변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런함수는 클래스 템플릿 안에 프렌드 함수로서 정의하자