# Template 参数推导机制
在算法中运用 迭代器(iterator)
时,很可能会用到其相应的型别。在 C++
中支持 sizeof()
并不支持 typeof()
,即使动用 RTTI
性质中的 typeid()
也只能获得型别名称而不能用作变量声明。
# 解决方法一:利用 template 的参数推导机制
template <class I, class T> | |
void fun_1(I iter, T t) { | |
T tmp = 0; // 这里解决了问题。T 就是指针所指之物的型别。 | |
// 省略... | |
} | |
template <class I> | |
inline void fun_2(I iter) { | |
fun_1(iter, *iter); //fun_2 的工作全部移动到 fun_1 上 | |
} |
上面虽然推出了 T
的型别,但是如果 T
必须用于函数的返回值,上面的参数推导就无法解决了。于是引入接下里的方法。
# 解决方法二:使用内嵌类型
template <class T> | |
struct Myiterater { | |
typedef T value_type; // 内嵌型别声明 | |
T* ptr; | |
MyIter(T* p = 0) : ptr(p) {} | |
T& operator*() const {return *ptr;} | |
}; | |
template <class I> | |
typename I::value_type // 这行是 func 的回返值型别 | |
func(I iter) { | |
return *iter; // 不但推出了参数而且可以用于函数返回值的推导 | |
} |
这里需要使用 typename
的原因是:因为 T
是一个 template
参数,在被编译器具现化之前,编译器对 T
一无所知,换句话说,编译器此时并不知道 MyIter<T>::value_type
是个型别, typename
就是告诉编译器这是一个型别,如此才能顺利通过编译。
但并不是所有的迭代器都是 class type
,比如原生指针就不能用此方法。而 STL
要求必须能接受原生指针作为迭代器,因此引出最后的方法:template 偏特化。
# 解决方法三:template 偏特化
template <typename T> | |
class C { | |
typedef T value_type; // 允许接受 T 为任何型别 | |
}; | |
template <typename T> | |
class C<T*> { | |
typedef T value_type; // 仅适用于 T 为原生指针,如 int * 的情况 | |
}; | |
template <typename T> | |
class C<const T*> { | |
typedef T value_type; // 针对指向常量对象的指针 | |
}; |
# Traits 编程
介绍完上面的 template
推导,接下来进入重头戏。下面这个 class template
专门用来萃取迭代器的特性。上面介绍过的 value_type 属于迭代器的特性之一,下面我们一一介绍。
最常用到的迭代器相应型别有五种。如果希望你所开发的容器能与 STL
水乳交融,一定要为你的容器的迭代器定义这五种相应型别。
# value_type
所谓 value_type
,是指迭代器所指对象的型别。任何一个打算与 STL
算法有完美搭配的 class
,都应该定义自己的 value_type
内嵌型别。
template <class I> | |
struct iterator_traits { | |
// 如果 I 定义有自己的 value_type,则萃取出的 value_type = I::value_type | |
typedef typename I::value_type value_type; | |
}; |
那么上面 解决方法二
中的 func()
可改写成下面这样:
template <class I> | |
typename iterator_traits<I>::value_type // 这一整行是函数回返型别 | |
func(I iter) { return *iter; } |
# difference_type
difference_type
用来表示两个迭代器间的距离,因此也可以用来表示一个容器的最大容量。例如 count()
其返回值必须使用迭代器的 differnece_type
。
针对相应型别 difference_type
, traits
的如下两个 (针对原生指针而写的) 特化版本,以 C++
内建的 ptrdiff_t
(定义于 cstddef
头文件〉作为原生指针的 difference_type
:
template <class I> | |
struct iterator_traits { | |
typedef typename I::difference_type difference_type; | |
}; | |
// 针对原生指针而设计的偏特化版 | |
template <class T> | |
struct iterator_traits<T*> { | |
typedef ptrdiff_t difference_type; | |
}; | |
// 针对原生的指向常量的指针 | |
template <class T> | |
struct iterator_traits<const T*> { | |
typedef ptrdiff_t difference_type; | |
}; |
现在当我们需要任何迭代器工的 difference_type
,可以这么写:
typename iterator_traits<I>::difference_type |
# reference_type
从 “迭代器所指之物的内容是否允许改变” 的角度观之,迭代器分为两种:不允许改变 “所指对象之内容” 者,称为 constant iterators
,例如 const int *pic
;允许改变 “所指对象之内容” 者,称为 mutable iterators
,例如 int* pi
。当我们对一个 mutabie iterators
进行提领操作时,获得的不应该是一个右值 ( rvalue
),应该是一个左值 ( lvalue
),因为右值不允许賦值操作,左值才允许:
int* pi = new int(5); | |
const int* pci = new int(9); | |
*pi = 7; // 对 mutable iterator 进行提领操作时,获得的应该是个左值,允许賦值 | |
*pci = 1; // 这个操作不允许,因为 pci 是个 constant iterator, | |
// 提领 pci 所得结果是个右值,不允许被赋值 |
C++
中函数如果要传左值,都是以 by reference
的方式进行,所以当 p
是个 mutable iterators
时,如果其 value_type
是 T
,那么 *p
的型别不应该是 T
,应该是 T&
。这里所讨论的 *p
的型别,即所谓的 reference_type
。
# poniter_type
pointers
和 references
在 C++
中有非常密切的关连。 如果 “传回一个左值,令它代表 p
所指之物” 是可能的,那么 “传回一个左值,令它代表 p
所指之物的位址” 也一定可以。 我们能够传回一个 pointer
,指向迭代器所指之物。
Item& operator*() const { return *ptr; } | |
Item* operator->() const { return ptr; } |
Item&
便是 reference_type
,而 Item*
便是 pointer_type
。
# iterator_category
等我实现好前面的,看完书这部分再更新。