条款46:需要类型转换时请为模板定义非成员函数
考虑之前的例子,对于操作符重载:
class Rational{
public:
Rational() :x(0){}
Rational(int x_) :x(x_){}
Rational &operator*(const Rational& rhs)
{
x *= rhs.x;
return *this;
}
int x;
};
int main()
{
Rational a(2);
Rational b = a * 2;
cout << b.x << endl;//ok 4
//Rational c = 2 * a << endl;//error
system("pause");
return 0;
}
在条款24中,说明希望将operator*声明为非成员函数:
class Rational{
public:
Rational() :x(0){}
Rational(int x_) :x(x_){}
int x;
};
const Rational operator*(const Rational &l, const Rational &r)
{
return Rational(l.x*r.x);
}
int main()
{
Rational a(2);
Rational b = a * 2;
cout << b.x << endl;//ok 4
Rational c = 3 * a ;//ok
system("pause");
return 0;
}
但是,如果我们将上述代码改成template格式,可能出问题:
template <class T>
class Rational{
public:
Rational() :x(0){}
Rational(T x_) :x(x_){}
T x;
};
template <class T>
const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
{
return Rational(l.x*r.x);
}
int main()
{
Rational<int> a(2);
Rational<int> b = a * 2;//error
system("pause");
return 0;
}
第一个是实参是Rational,通过此推导出T为int
传递的第二个实参类型是int,编译器无法推算出T类型,这里不具备构造函数隐式类型转换的条件。
解决的办法是用友元函数:
template <class T>
class Rational{
public:
Rational() :x(0){}
Rational(T x_) :x(x_){}
T x;
friend const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
{
return Rational(l.x*r.x);
}
};
int main()
{
Rational<int> a(2);
Rational<int> b = a * 2;//ok 4
cout << b.x << endl;
system("pause");
return 0;
}
通过第一个实参Rational具现化出对应类,接受Rational参数的friend也被具现化出来,这样就满足了隐式转换的条件了。
但是有个缺陷,那就是在类内定义可能会被编译器内联,如果不想inline,我们可以不在operation*
中做事情,改调用外部函数:
template <class T>
const Rational<T>func(const Rational<T> &l, const Rational<T> &r)
{
return Rational<T>(l.x*r.x);
}
template <class T>
class Rational{
public:
Rational() :x(0){}
Rational(T x_) :x(x_){}
T x;
friend const Rational<T> operator*(const Rational<T> &l, const Rational<T> &r)
{
return func(l, r);
}
};
int main()
{
Rational<int> a(2);
Rational<int> b = 2 * a;//ok 4
cout << b.x << endl;
system("pause");
return 0;
}
条款47:请使用traits classes表现类型信息
看到traits我很亲切,萃取器,早先学习STL源码剖析的时候,对这个还算理解得透彻。
回顾一下STL的迭代器:
迭代器类型(iterator_category
)是迭代器五种型别之一:
迭代器被分为5类:
Input :只读
Output :只写
Forward :可读写
Bidirectional:可双向读写
Random Access:可以随机访问
通过is-a
的关系表示
//继承关系
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
为什么这里都是tag了,事实上,在STL里面只需要一个类型去标记一下即可。在EffectiveC++中,举了advance的例子,advance函数用来执行it+=d
操作,但是除了random_access
之外,其他迭代器类型并不支持+=
,只能执行++
或者--
操作执行d次。显然,advance函数需要根据迭代器类型进行重载。
看看STL里面的实现:
//1.
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
__STL_REQUIRES(_InputIterator, _InputIterator);
__advance(__i, __n, iterator_category(__i));
}
外部接口advance
转调用__advance
,这里模板有两个class,一个是迭代器类型,一个是步进distance的类型。
抛开__advance
先不看,注意到一个函数iterator_category
//2.
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }
先不管函数的返回值,外部接口iterator_category
调动了__iterator_category
:
//3.
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category//一整行作为函数返回类型
__iterator_category(const _Iter&)
{
typedef typename iterator_traits<_Iter>::iterator_category _Category;
return _Category();//创建一个临时对象 例如int()创建一个临时的int对象。
}
我们看到,Iterator_category
通过内部转调用,最终能够返回一个iterator_categor的临时对象,这个临时对象作为__advance
的第三个实参。
现在来看:
template <class _Iter> typename iterator_traits<_Iter>::iterator_category
是怎么来的:
//4.
template <class _Iterator>
struct iterator_traits {
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
//原生指针的特化版本
template <class _Tp>
struct iterator_traits<_Tp*> {
typedef random_access_iterator_tag iterator_category;
typedef _Tp value_type;
typedef ptrdiff_t difference_type;
typedef _Tp* pointer;
typedef _Tp& reference;
};
//const原生指针的特化版本
template <class _Tp>
struct iterator_traits<const _Tp*> {
typedef random_access_iterator_tag iterator_category;
typedef _Tp value_type;
typedef ptrdiff_t difference_type;
typedef const _Tp* pointer;
typedef const _Tp& reference;
};
原生指针的迭代器类型是支持双向随机访问,因此是random_access_iterator_tag
,其他的迭代器例如vector<int>::iterator
的类型就在其class中声明了,这里直接使用::
访问即可。
到此,先不管__advance
的内部实现,我们来看一下整个逻辑:
1.advance接口转调用__advance
2.在__advance中有一个iterator_category(i)参数
3.iterator_category(i)调用了__iterator_category(i)
4.__iterator_category通过i推断出模板类型,并调用iterator_traits获取迭代器的iterator_category型别。
5.返回一个上述型别的临时对象。
最后我们来看看__advance的实现,上文提到,由于不是所有的iterator都支持+=
操作,因此这一定是一个重载的过程:
//__advance的重载版本,这里只有类型,没有临时变量(因为不必要)仅仅用来重载
//重载 __advance input_iterator
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
while (__n--) ++__i;
}
//BidirectionalIterator重载版本
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n,
bidirectional_iterator_tag) {
__STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
if (__n >= 0)//可以双向
while (__n--) ++__i;
else
while (__n++) --__i;
}
//RandomAccessIterator重载版本
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n,
random_access_iterator_tag) {
__STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
__i += __n;
}
我们看到,第三个参数虽然返回了一个临时变量,但是在重载的版本里面并没有用到,只是通过类型来进行重载。
那么问题来了,Forward版本的__advance去哪了?
原来由于继承关系,当参数无法完全匹配时,会自动传递调用 Input的版本。
最后,上文说到使用traits拿到iterator_category,是这样的访问顺序:
list<>::iterator::iterator_category
可以想见,在STL中应该有这样的代码:
template <>
class list{
public:
class iterator{
typedef bidirectional_iterator_tag iterator_category;
};
};
条款48:认识template元编程
以前真的没有接触过,模板元编程真的是amazing.
先看一个求阶乘的例子:
template <unsigned n>
struct Factorial{
enum { value = n*Factorial<n - 1>::value };//enum hack
};
template <>
struct Factorial < 0 > {
enum { value = 1 };//enum hack
};
int main()
{
cout << Factorial<5>::value << endl;//120
system("pause");
return 0;
}
这个例子就是模板元编程里面的"hello world"
了…
TMP(模板元编程)可以将工作由运行期移到编译器,因而得以实现早期错误侦测和更高的执行效率。