——这篇是直接根据侯捷老师的书写的,几乎没有自己加工的部分,不过也是学习的总结吧
Traits编程技法
按照顺序,这次应该是迭代器Iterator的内容了,然而Iterator涉及到一个重要的技巧就是Traits编程技法;它还是值得单独一章来介绍一下的。
一 获取Iterator的相应类型(associate type)
在使用Iterator时,可能需要知道它的相应类型,也就是Iterator指向的变量的类型,在C/C++语言中,如果要获取一个变量的大小可以使用sizeof()操作符。然而如果想要获取一个指针指向的变量类型该如何做呢,可惜它没有一个typeof()操作符供我们程序员使用。
利用template的引数/参数推导(argument deducation)是一个解决问题的好方法,仅将func函数作为一个包装,而把实际的操作放在一个函数func_impl里面完成。一旦func()函数被调用,编译器就自动进行引数推导,自动导出类型T。
template <class I>
inline void func(I iter)
{
func_impl(iter, *iter); // 一层封装
}
template <class I, class T>
void func_impl(I iter, T t)
{
T tmp; // 在本例中,t就是int类型
tmp = t;
cout<<tmp<<endl; // tmp为int类型,可以直接输出
}
int main()
{
int i = 4;
func(&i);
return 0;
}
看上去不错,虽然多了一层包装,但是还是可以工作的很好。好了,现在想想另一种情况,如果要将这个类型作为一个函数,比如上面的func的返回类型,该怎么办呢。毕竟引数推导导出的只是引数,没有办法应用于函数的返回值。看来我们需要另外的方法来解决这一问题,这就引出了本章的一个重要技巧Traits编程技法。
二 Traits编程技法初见
采用nested type(巢状型别)似乎是个不错的注意,如下所示:
template <class T>
class Iterator
{
public:
typedef T value_type;
T *m_ptr;
Iterator(T *p = 0) : m_ptr(p) {}
T& operator *() const {return *m_ptr;}
// ...
};
template <class I>
typename I::value_type func2(I iter)
{
return *iter;
}
int main()
{
int *p = new int(8);
Iterator<int> iter(p);
cout<<func2(iter)<<endl;
delete p;
return 0;
}
这里func2函数的返回值前加上了一个typename,这是因为在template T实例化之前,编译器对T一无所知,并不知道Iterator<int>::value_type代表的是一个函数,变量还是类型。关键字typename就是告诉编译器说这是一个类型,以使得编译通过。
看起来不错,但是这里还有一个隐晦的陷阱:并不是所有的迭代器都有value_type,编译器内嵌类型(原生指标)就没有,这样编译就不能通过,但是STL必须接受原生指标作为一种迭代器,这需要另外的技巧,它就是模板偏特化(template partial specialization)。