条款46: 需要隐式类型转换时请为模板定义非成员函数

这个条款可以看成是条款24的续集,我们先简单回顾一下条款24,它说了为什么类似于operator *这样的重载运算符要定义成非成员函数(是为了保证混合乘法2*SomeRational或者SomeRational*2都可以通过编译,2不能同时进行隐式类型转换成某个Rational,再作this用)。

所以我们一般(并非不定义成友元就编译不过,而是为了更合理)将之定义成友元函数,像下面这样:

class Rational
{
private:
    int numerator;
    int denominator;
public:
    Rational(int n = 0, int d = 1): numerator(n), denominator(d){assert(denominator != 0);}
    int GetNumerator() const{return numerator;}
    int GetDenominator() const {return denominator;}
    friend const Rational operator* (const Rational& r1, const Rational& r2);
};
const Rational operator* (const Rational& r1, const Rational& r2)
{
    return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
}

现在我们来引入模板,可以像下面这样写,注意这里的operator*是一个独立的模板函数:

template 
class Rational
{
private:
    T Numerator;
    T Denominator;

public:
    Rational(const T& Num = 0, const T& Den = 1) : Numerator(Num), Denominator(Den){}
    const T GetNumerator() const
    {
        return Numerator;
    }

    const T GetDenominator() const
    {
        return Denominator;
    }

    string ToString() const
    {
        stringstream ss;
        ss << Numerator << "/" << Denominator;
        return ss.str();
    }
};

template 
const Rational operator* (const Rational& a, const Rational& b)
{
    return Rational(a.GetNumerator() * b.GetNumerator(), 
        a.GetDenominator() * b.GetDenominator() );
}

但下面main函数的两行却都不能通过编译:

int main()
{
    Rational a(3, 5);
    Rational c = a * 2; // 不能通过编译!
    c = 2 * a;               // 不能通过编译!
    cout << c.ToString() << endl;
}

原因是编译器推导T出现了困难,a * 2在编译器看来,可以由a是Rational将T推导成int,但是2是什么,理想情况下编译器会尝试将它先转换成一个Rational,并将T推导成int,但事实上编译器在“T推导过程中从不将隐式类型转换函数纳入考虑”。所以无论是a * 2还是2 * a都是不能通过编译的,一句话,隐式转换+推导T不能被同时被编译器接受。

解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先满足好一个条件,再由编译器执行另一个过程好了。

如果把这个operator*在template class里面进行声明,就可以了。这是因为,a被声明为一个Rational,class Rational于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational参数)也就被自动声明出来。

因此我们可以这样来改:

template 
class Rational
{
    …
    /* 在一个class template内,template名称可被用来作为“template和其参数”
    * 的简略表达式,所以在Rational内我们可以只写Rational而不必写
    * Rational 
    */
    friend Rational operator* (const Rational& a, const Rational& b);
};

template 
const Rational operator* (const Rational& a, const Rational& b)
{
    // 这里友元函数的声明并不是用来访问类的私有成员的,而是用来进行事先类型推导的
    return Rational(a.GetNumerator() * b.GetNumerator(), 
        a.GetDenominator() * b.GetDenominator() );
}

注意声明部分,我们添加了一个友元函数的声明,果然编译通过了,但链接时又报错了,原因是链接器找不到operator*的定义,这里又要说模板类中的一个特殊情况了,它不同与普通的类,模板类的友元函数只能在类中实现,所以要把函数体部分移至到类内,像下面这样:

template 
class Rational
{
    …
    friend Rational operator* (const Rational& a, const Rational& b)
    {
        return Rational (a.GetNumerator() * b.GetNumerator(),
            a.GetDenominator() * b.GetDenominator());
    }
    …
}

这下编译和链接都没有问题了。

你可能感兴趣的:(条款46: 需要隐式类型转换时请为模板定义非成员函数)