《Effective C++》条款 46 补充
问题
《Effective C++》条款 46 的标题是:需要类型转换时请为模板定义非成员函数。本节作者定义了一个有理数类,希望他能做如下运算:
Rational<int> a(1, 3);
Rational<int> b = a * a;
Rational<int> c = a * 3;
Rational<int> d = 3 * a;
类的定义如下:
template <typename T>
class Rational{
public:
Rational(T numerator, T denominator=1): numerator_(numerator), denominator_(denominator){}
T numerator() const{ return numerator_; }
T denominator() const{ return denominator_; }
private:
T numerator_;
T denominator_;
};
解法
为了实现 b = a * a
可以重载类的 operator*
方法:
Rational operator*(const Rational &rhs) const{
Rational ret(numerator_ * rhs.numerator_, denominator_ * rhs.denominator_);
return ret;
}
而要想让 Retional
类可以和 int
或者 double
等直接进行运算,如 a * 3
,需要这里的 3
可以隐式转换为 Retional
,因此 Retional
需要有一个可以接受单参数,且不能是 explicit
的构造函数。目前的类定义满足此要求。
但是为了实现 d = 3 * a
,以上的工作都失去了意义,为此需要定义如下运算符:
template <typename T>
Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs){
return Rational<T>(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
这个时候,b = a * a
可以正常工作,但是 c = a * 3
和 d = 3 * a
会出错。原因很简单,模板在实例化的时候是不会做由 int
到 Rational<int>
的转换的。 为了支持这两种运算,可以定义如下模板函数:
template <typename T>
Rational<T> operator*(const T &lhs, const Rational<T> &rhs){
return Rational<T>(lhs) * rhs;
}
template <typename T>
Rational<T> operator*(const Rational<T> &lhs, const T &rhs){
return lhs * Rational<T>(rhs);
}
在这两个模板函数内部,显示地进行了 T
到 Rational<T>
的转换。
更精简的解法
如果 T
可以隐式转换为 Rational<T>
,那么就只需要一个函数。为了能够隐式转换,这个函数不能是模板函数。但是此函数又必须支持多种类型,一种方法是把他定义在类里面。为了在类里面定义一个普通函数,它就只能是友元的。
template <typename T>
class Rational{
friend Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs);
public:
Rational(T numerator, T denominator=1): numerator_(numerator), denominator_(denominator){}
//...
}
这里只在类里面做了声明,还缺少定义。于是在类外部写下如此定义:
template <typename T>
Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs) {
// ...
}
这就再度把自己引入了错误的深渊。因为 friend
声明的函数不是一个模板函数,而上面却定义了一个模板函数。结果是友元函数,没有定义。
friend Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs);
解决的办法就是在类里面完成对友元函数的定义:
template <typename T>
class Rational{
friend Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs){
return Rational<T>(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
// ...
}
这样以来,在对类模板实例化的时候,就对这个函数进行实例化。
总结
在编写模板类的时候,如果需要支持隐式类型转换,那就不能依赖于模板函数,因为模板函数不会做隐式类型转换。此时需要定义一个非模板函数,并把它作为 friend
函数,并在类里面完成函数的定义。因为定义在类中的函数会是内联的,因此可以把具体的操作交给类的某个方法来完成。