谈谈Traits技术

在说 Traits 之前, 需要先说一下 typedef. 我在大一的时候就被问过一个问题

1
2
3
// 这两句有没有区别
#define Int int;
typedef int Int;

答案当然是有, 但是我当时只能死记硬背的答出 typedef 不是简单的替换, 也不是改名, 而宏定义则仅仅是简单的文本替换. 但是这背后的原理并不是很清楚.

直到我读了 STL Source Code 发现其实现中 大量的 typedef, 然后不断比对后才得出了对它的新理解————typedef 可以将当前的类型进行「保存」.

例如将 vector 的定义简短化

1
2
3
4
5
template <typename T...>
class vector {
public:
typedef T value_type;
}

看到了吗? template 里并不知道 T 的类型, 但是我通过 typedef 将它保存在一个叫 value_type 的元素中. 现在 value_type 便有了跟 T 一样的功能, 可以作为类型去定义新的变量.

那这用处在哪里呢? 我们要清楚, STL 的实现中, container 和 algorithm 是根本不知道对方的存在的, 而让他俩进行通信需要在中间加一个介质——iterator. 这样就出现了新的问题, algorithm 使用 itertator 操作 container, iterator 操作 container 的元素需要知道元素的类型 还要根据 container 的性质确定 iterator 的类型等等.

这时候就用到了 traits 技术, 实际上这可以说是一种编程策略, 利用模版技术获取类型, 上面的 vector 简短定义中我在 public 里用 typedef 将 vector 元素的类型 T 保存在了 value_type 里. 然后我只需要写一个东西将这个 value_type 给拿出来就可以了

在正式的说 traits 之前, 还要解释一下 typename (在《Effective C++》第 42 条比较清楚的介绍了它), typename 用作模版参数时, 与 class 没有什么区别, 但是在一些情况下(嵌套从属类别名称前) 需要加上 typename 关键字, 因为会存在一种情况, 例如 C::value_type 不是个类型, 而是个变量, 那用一个变量去定义一个变量就是语法错误了.

1
2
3
4
template <class C>
struct _traits {
typedef typename C::value_type value_type;
}

就像上面的代码这样, 然后我就得到了一个通用的 traits class(尽管实现为 struct)可以用来获取 container 的元素的类型,当然 STL 里 traits 的实现比这个复杂得多, 这只是举一个例子.

然后测试一下这个简易版 traits 的作用

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
template <class C>
struct _traits {
typedef typename C::value_type value_type;
};

void foo(int val) {
std::cout << "type of val is int" << std::endl;
}
void foo(double val) {
std::cout << "type of val is double" << std::endl;
}
void foo(char val) {
std::cout << "type of val is char" << std::endl;
}

int main(int argc, char* argv[]) {
_traits<std::vector<int>::iterator>::value_type val1;
foo(val1);
_traits<std::vector<char>::iterator>::value_type val2;
foo(val2);
}

/*
type of val is int
type of val is char

*/
script>