Fork me on GitHub

traits编程技法

STL之traits编程技法的理解

注:很多内容节选自《STL源码剖析》,见上自己的理解,特此记录

  • traits又被叫做”特性萃取技术”,用于提取被”被传进来的对象对应的返回类型,让同一个接口实现对应的功能。因为STL的算法和容器是分离的,两者通过迭代器连接,算法的实现并不知道传进来的是什么,萃取器相当于接口和实现之间加一层封装,来隐藏一些细节并协助调用合适的方法。

    迭代器的类型

    我们知道迭代器有五种类型,分别是
  1. value_type(迭代器所指对象的类型)
  2. difference_type(用来表示两个迭代器之间的距离)
  3. reference_type(迭代器所指对象的引用)
  4. point_type(迭代器所指的对象)
  5. iterator_category(标识迭代器的移动特性和可以对迭代器进行的操作,大致可以分为五类)
    Input iterator
    Output iterator
    Forward iterator
    Bidirectional iterator
    Random Access iterator

声明内嵌型别

考虑一下,当我们需要实现某个函数时,且该函数的返回类型必须是value_type是,该怎么办?我们不能使用”template 参数推导机制”,毕竟推导机制推导的是参数,无法推导函数的返回值型别。
在这里我们就可以用到声明内嵌型别,来解决使用value_type用于函数的返回类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template <class T>
struct MyIter{
typedef T value_type; //内嵌型别声明
MyIter(T *p=0):ptr(p) {}
T& operator*() const {return *ptr;}
private:
T *ptr;
};

//使用value_type用于函数返回类型
template <class T>
typename I::value_type func(I ite)
{
return *ite;
}


//...
int main()
{
Myiter<int> ite(new int(8));
cout<<func(ite)<<endl; //输出:8
}
```
func函数中的typename用意是在告诉编译器I::value_type是一个型别,因为T是一个template参数,在它被编译器具现化之前,编译器对T一无所悉,编译器不知道I::value_type代表的是一个型别还是一个member function或者是data member。
这样看起来结局了我们的问题,但是有个隐晦的陷阱:并不是所有的迭代器都是class type。原生指针就不是,如果不是class type,就无法为它定义内嵌型别。有没有好的解决办法呢?偏特化可以为我们解决这个问题。
## 模板偏特化
《STL源码剖析》一书上对模板偏特化的意义大致解释是:
> 如果class template拥有一个以上的template参数,我们可以针对其中某个(或数个,但非全部)template参数进行特化工作。换句话说,我们可以在泛化设计中提供一个特化版本。

//萃取器
template
struct iterator_traits{
typedef typename T I::value_type value_type;
};

//偏特化版
template
struct ierator_traits<T*>{
typedef T value_type;
};

//偏特化过后的func函数
template
typename iterator_traits::value_type //这一整行是函数返回型别
func(I ite)
{
return *ite;
}

int main()
{
int a=8;
int p=&a;
cout<<func(p)<<endl; //传的类型是int

}

1
上面我们通过偏特化版就可以获取原始指针的型别用来定义函数返回型别,可能会问如果原始指针是个pointer-to-const怎么办呢?没关系再偏特化一个版本就好了

template
struct iterator_traits{
typedef T value_type;
};
`
以上就是stl中使用的traits技法,traits就像一台”特性萃取机”,萃取各个迭代器的相应型别。

您的赞赏是对我最大的支持,谢谢!