天天看点

C++ Traits编程技法--从迭代器的设计看参数推导与类型推导 迭代器与相应类型推导 解决方法一函数模版的参数推导机制 解决方法二使用嵌套依赖类型(nested depended name) 特性萃取机器–偏序化实现支持原生类型 STL中迭代器的设计

在写作泛型函数或代码时,我们可能存在这样的需要:与参数相关的其它类型,比如一个迭代器的值的类型,在算法中运用迭代器时,很可能会用到其也叫相应类型(associate type)。

什么是相应类型?

迭代器所指之物的类型就是其中一个。如果我们的算法中有必要声明一个变量,以”迭代器所指对象的类别”为型号。

本文要向大家展示一个函数模板推导机制使用技法,这个在stl的迭代器和许多排序算法中广泛使用。

考虑一个情况,我们在写一个泛型函数,它接受一对迭代器,要做的事就是对这一对迭代器之间的元素进行排序,其中将出现这幕:我需要对两个值进行交换。不知道大家有没有写过这样的代码,现在的问题是如何实现这两个值的交换?

如:

1

2

3

4

5

很显然我们需要了解到迭代器所指向的具体类型,这该怎么好呢

毕竟如果c++没有只支持sizeof(),并不支持typeof()

有些人说了我们有rtt1机制中的typeid(),是的很好,但是那获取到的也只是类型名称name,而不是类型type,更不可能来做变量声明

解决的方法:利用函数模版(function template)的参数推导(argument deducation)机制。

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

参数推导机制,以func()为对外接口,却把实际的操作全部置于func_impl()之中,由于func_impl()是一个函数模版(function template),一旦被调用,编译器会自动进行template的参数推导,于是导出类型t的问题,顺利得到了解决。

但是迭代器相应类型不只是“迭代器所指对象的类型”一种而已

根据经验,最常用的相应类型有5种,然而并非任何情况下任何一种都可利用函数模版的参数推导机制来取得。

因此我们需要更一般的解法。

也称为traits编程技法,是stl中广泛使用的技巧

迭代器所指对象的类型,称为迭代器的value type,上述的参数类型推导技巧虽然可用于value type,却并不对所有的相应类型可用。

前面的情况,通过隐藏了中间层,通过函数模版自己去推导参数类型,我们的接口也只是关心迭代器而不在意其指向的,但是如果我们的对外接口不能不了解到其相应类型的时候,这种方法就不适用了。

比如如下情况,万一value type必须用于函数的返回值,就束手无策了。

毕竟函数的“template参数推导机制”推而导之的只是参数,无法推导函数的回返值类型。

我们需要其他方法,有什么好的方法呢,还记得之前提到的typename的以及嵌套依赖类型(nested depended name)么。

那不就可以么,我们不妨一试

29

30

31

32

33

34

35

36

37

38

func()的返回值被定义为typename iter::value_type

内嵌类型被定义为 typedef t value_type

由于t是一个template参数,在它被编译器具体化之前,编译器对t一无所知,换句话说,编译器此时并不知道<code>myiter&lt;t&gt;::value_type</code>代表的是一个类型还是一个成员函数或者说是一个数据成员,

而这个typename关键字的用意就在于告诉编译器这个是一个类型,如敝才能顺利通过编译。

这样我们的问题似乎得到了解决,但是这里有一个隐晦的陷阱:并不是所有的迭代器都是类class type,比如有原生的指针就不是。如果不是class type,就无法为它定义内嵌类型。

但是这显然不是我们做模版类所期待的,在stl(甚至于整个泛型思维)绝对接受原生的指针作为一种迭代器,所有我们上面的方法还不够,有没有什么方法可以让上述的一般化概念针对特定情况(比如原生的指针类型)做特殊化处理呢?

那么这就是template partial specialization可以完成的

如果class template拥有一个以上的template参数,那么可以针对其中某个(或者多个,但是不能全部)的参数进行特化工作。

换句话说,我们可以在泛化设计中提供一个特化的版本。即通过将泛化版本中的某些template参数赋予明确的指定。

关于函数模版的特化问题,不是我们今天的重点,要了解详细信息,请参见 <a href="http://blog.csdn.net/gatieme/article/details/50953564">c++模板的特化详解(函数模版特殊,类模版特化)</a>

通过针对任何template参数的模版,进行特化,得到一个限制了参数对象的特化版本。

有了这个利器,我们就可以解决前述的”内嵌类型”未能解决的问题,先前的问题是,原生的指针并非class,因此无法为他们定义内嵌型别。

现在我们可以通过对迭代器指针为参数的特化对象,来设计可以接收原生指针的特殊迭代器。

因此我们设计出一个中间层,并添加一个value type内嵌对象

这个所谓的traits迭代器说明,如果iter定义有自己的value type,那么就通过这个traits的作用,萃取出来的value_type成员就是iter::value_type。

换句话说,如果i定义有自己的value type,那么先前的那个func就成为

增加iterator_traits这一中间层,现在我们为iterator_traits添加特化版本。

通过这种方式,原生的指针比如int *虽然不是类对象class type,亦可通过traits取其value type。

于是我们的迭代器支持原生的指针类型了。

但是这样子够了么,请看下面的例子,

当我们使用指向const常数对象的指针时,看看发生了什么

获得到的是一个const int,而不是int。很显然这个并不是我们所期望?

比如我们利用这种方式来声明一个临时变量,使其类别与迭代器的value type相同,而现在,声明一个无法赋值(因const之故)的暂时变量,并没有什么卵用。

这一切的一切,就只是因为我们获取到的迭代器的value type是一个pointer-to-const

现在ok了,不管是针对迭代器myiterm,还是原生的指针int ,甚至是const int ,都可以通过traits取出正确的value type

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

最后通过traits这个特性萃取机角色,可以巧妙的萃取出各个迭代器的属性。

这点我们可以在stl源码的找到对应的设计,参见 <code>stl_iterator_base.h</code>

由于

转载:http://blog.csdn.net/gatieme/article/details/50950726

继续阅读