标签:
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; };
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){ ... }
那么可能会有以下代码:
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){ ... }
现在对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()); } ... };
此时对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); ... };
许多编译器为了实行template具现化,要求把template定义式放在头文件内,因此可能需要在头文件内定义doMultiply,但doMultiply可以不为inline.
Effective C++ 条款46 需要类型转换时请为模板定义非成员函数
标签:
原文地址:http://www.cnblogs.com/reasno/p/4802040.html