Effective C++ 条款46 需要类型转换时请为模板定义非成员函数

1. 条款24举出一个Rational的例子,来说明为什么只有non-member函数才有能力"在所有实参身上实施隐式类型转换".Rational的定义如下:

class Rational{
public:
    Rational(int numerator=0,int denominator=1);
    int numerator()const;
    int denominator()const;
private:
    int numerator;
    int denominator;
};
View Code

operator*声明为Rational的non-member函数:

const Rational operator*(const Rational& lhs,const Rational& rhs);

在未涉及到模板时,这么做是正确的,如果将Rational升级为类模板,可能像这样:

template<typename T>
class Rational{
public:
    Rational(const T& numerator=0,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){
    ...
}
View Code

    那么对于以下代码:

Rational<int> oneHalf(1,2);
Rational<int> result=onHalf*2;

    看起来"onHalf*2"似乎应该使函数模板operator*具现化并调用它,但实际上编译不通过,根本原因在于编译器"在template实参推到过程中从不将隐式转换纳入考虑":转换函数在函数调用过程中的确被使用(如果operator*是一个函数而不是函数模板的话),但在调用一个函数之前,必须要知道那个函数存在,而为了知道它,必须先为相关的function template推导出参数类型(然后才可将适当的函数具现化出来).然而template实参推导过程中并不考虑采纳"通过构造函数而发生的"隐式类型转换!

2. 只要利用一个事实就可以改变这种现状:template class内的friend声明式可以指涉该特定函数.也就是说Rational可以声明operator*为它的一个friend函数,class template并不依赖于template实参推导,因此编译器总是能够在class Rational具现化时得知T,因此,令Rational<T> class声明适当的operator*为其friend函数可简化整个问题:

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

    现在对operator*的调用就可以通过编译了,发生的改变是:oneHalf被声明时,class Rational<int>被具现化出来,而作为过程的一部分,friend函数operator*也就被作为一个函数而非函数模板自动声明出来,因此编译器可以在调用它时使用隐式转换函数.

    此时还未结束,以上代码虽然通过了编译,但却无法链接——Rational<int> operator*(const Rational<int>&lhs,const Rational<int>&rhs)已经被声明出来,但却没有定义.使用template是行不通的,因为此当Rational被具现化时,operator*只是作为一个普通的函数声明被具现化出来,编译器不会认为它和operator*函数模板有关联而为它具现化出一个函数实体.

    解决办法就是将operator*函数本体合并至其声明式内:

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

    此时对operator*的调用可编译链接并执行.

3. 在本条款中,friend的作用不再是提升函数或类的访问权限,而是使类型转换发生在所有参数身上:为了使该函数被自动具现化,需要把它声明在类内部,而在类内部声明non-member函数的唯一办法就是令它成为一个friend.

    由于operator*需要在Rational内部定义,它被默认声明为inline,为了使这种inline声明带来的冲击最小(本例中operator*已经是一个单行函数,但更复杂的函数也许需要这样),可以使它调用一个定义域class外部的辅助函数,由该辅助函数完成实际功能:

template<typename T>
const Rational<T> doMultiply(const Rational& lhs,
                                             const Rational& rhs){
    ...
}
template<typename T>
class Rational{
public:
    friend Rational operator*(const Rational& lhs,
                                           const Rational& rhs){
        doMultiply(lhs,rhs);
    ...
};
View Code

    许多编译器为了实行template具现化,要求把template定义式放在头文件内,因此可能需要在头文件内定义doMultiply,但doMultiply可以不为inline.

原文地址:https://www.cnblogs.com/reasno/p/4802040.html