독서노트Effective_C++_약관 46: 유형 변환이 필요한 경우 템플릿에 비구성원 함수를 정의합니다.

13279 단어 effective
이 조항은 조항24의 속편으로 볼 수 있다. 우리는 조항24를 간단하게 살펴보자. 이것은 왜 Operator*와 같은 재부팅 연산자를 비구성원 함수로 정의해야 하는지를 설명한다. (혼합 곱셈 2*SomeRational 또는 SomeRational*2는 모두 번역을 통해 번역할 수 있고 2는 은식 유형을 어떤 Rational로 동시에 변환해서this용으로 사용할 수 없음을 보장하기 위해서이다).
그래서 우리는 일반적으로 이를 우원 함수로 정의한다. 다음과 같다.
 1 class Rational
 2 {
 3 private:
 4     int numerator;
 5     int denominator;
 6 public:
 7     Rational(int n = 0, int d = 1): numerator(n), denominator(d){assert(denominator != 0);}
 8     int GetNumerator() const{return numerator;}
 9     int GetDenominator() const {return denominator;}
10     friend const Rational operator* (const Rational& r1, const Rational& r2);
11 };
12 const Rational operator* (const Rational& r1, const Rational& r2)
13 {
14     return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
15 }

이제 템플릿을 도입합니다. 아래와 같이 쓸 수 있습니다. 여기의operator*는 독립된 템플릿 함수입니다.
 1 template <class T>
 2 class Rational
 3 {
 4 private:
 5     T Numerator;
 6     T Denominator;
 7 
 8 public:
 9     Rational(const T& Num = 0, const T& Den = 1) : Numerator(Num), Denominator(Den){}
10     const T GetNumerator() const
11     {
12         return Numerator;
13     }
14 
15     const T GetDenominator() const
16     {
17         return Denominator;
18     }
19 
20     string ToString() const
21     {
22         stringstream ss;
23         ss << Numerator << "/" << Denominator;
24         return ss.str();
25     }
26 };
27 
28 template <class T>
29 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
30 {
31     return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
32         a.GetDenominator() * b.GetDenominator() );
33 }

그러나 다음main 함수의 두 줄은 컴파일을 통과할 수 없습니다.
1 int main()
2 {
3     Rational<int> a(3, 5);
4     Rational<int> c = a * 2; //
5     c = 2 * a;               //
6     cout << c.ToString() << endl;
7 }

왜냐하면 컴파일러가 T를 유도하는 데 어려움이 생겼기 때문이다. a*2는 컴파일러가 보기에 a는 Rational 로 T를 int로 유도할 수 있지만 2가 무엇인지. 이상적인 상황에서 컴파일러는 T를 Rational 로 먼저 변환하고 T를 int로 유도하려고 시도하지만 사실상 컴파일러는'T유도 과정에서 스텔스 형식 변환 함수를 고려하지 않는다'.그래서 a*2든 2*a든 모두 컴파일을 통과할 수 없고 한마디로 은식 변환 + 유도 T는 컴파일러에 의해 동시에 받아들여지지 않는다.
문제를 해결하는 사고방식이 이어서 생겼다. 컴파일러가 이 두 과정을 동시에 받아들일 수 없으니 미리 한 조건을 충족시키고 컴파일러가 다른 과정을 집행하도록 하자.
이operator*를templateclass에 넣으면 템플릿 클래스를 만드는 단계에서 T를 정합니다. 그러면 컴파일러가 스텔스 변환을 실행하면 됩니다.
그래서 우리는 이렇게 고칠 수 있다.
 1 template <class T>
 2 class Rational
 3 {
 4  5     friend Rational operator* (const Rational& a, const Rational& b);  6 };
 7 
 8 template <class T>
 9 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
10 {
11     //
12     return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
13         a.GetDenominator() * b.GetDenominator() );
14 }

빨간색 부분에 주의하여 우리는 유원 함수의 성명을 추가했는데 과연 번역이 통과되었지만 링크를 잘못 보고했다. 왜냐하면 링크기가operator*의 정의를 찾지 못했기 때문이다. 여기서 템플릿 클래스의 특수한 상황을 말하고자 한다. 이것은 일반적인 클래스와 다르다. 템플릿 클래스의 유원 함수는 클래스에서만 실현될 수 있기 때문에 함수체 부분을 클래스로 옮겨야 한다. 다음과 같다.
 1 template <class T>
 2 class Rational
 3 {
 4  5     friend Rational operator* (const Rational& a, const Rational& b)
 6     {
 7         return Rational (a.GetNumerator() * b.GetNumerator(),
 8             a.GetDenominator() * b.GetDenominator());
 9     }
10 11 }

이제 컴파일링과 링크에 문제가 없습니다.여기서 다시 한 번 말하자면 클래스 안으로 이동한 후에 T의 표지부는 쓰지 않아도 되지만 굳이 아래와 같이 써야 한다면 자연히 OK이다.
1 friend Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
2 {
3     return Rational<T>(a.GetNumerator() * b.GetNumerator(),
4         a.GetDenominator() * b.GetDenominator());
5 }

operator*에는 단지 한 마디가 있지만 friend 함수에 너무 많은 것이 있으면 보조 방법을 정의할 수 있다. 예를 들어DoMultiply(), 이DoMultiply는 클래스 밖으로 놓아서 실현할 수 있다. DoMultiply 자체는 혼합 곱셈(2*SomeRational 또는SomeRational*2)을 지원하지 않지만 Operator*에서 이미 은밀한 유형 전환이 진행되었기 때문에DoMultiply라는 단계는 문제가 없다.
 
마지막으로 요약:
"class template를 작성할 때,"이template와 관련된 "함수가"모든 매개 변수의 은밀한 형식 변환 "을 지원할 때, 그 함수를"class template 내부의friend 함수 "로 정의하십시오.

좋은 웹페이지 즐겨찾기