STL之迭代器

3.1迭代器设计思维STL 关键所在 

STL 的中心思想在于,将数据容器( containers)和算法( algorithms)分开,彼此独立设计,最后再以一帖胶着剂将它们撮合在一起。容器和算法的泛型化,从技术角度来看并不困难, C++ 的 class templates 和 function templates可分别达成目标。如何设计出两者之间的良好胶着剂,才是大难题。 

3.2迭代器iterator是一种 smart pointer 

迭代器是一种行为类似指针的对象而指针的各种行为最常见的是解引用和指向。

3.3迭代器相应型别(associated types 

算法之中运用迭代器时,很可能会用到其相应型别( associated type)。什么是相应型别?迭代器所指之物的型别便是其一。 假设算法中有必要声明一个变量,以「迭代器所指对象的型别」为型别,如何是好?

解决办法利用 function template 的自变量推导( argument deducation)机制。 

template <class I, class T>
void func_impl(I iter, T t)
{
T tmp; // 这里解决了问题。 T就是迭代器所指之物的型别,ᴀ例为 int
// ... 这里做原ᴀ func()应该做的全部工作
};
template <class I>
inline void func(I iter)
{
         func_impl(iter,*iter);// func 的工作全部移往 func_impl
}
int main()
{
int i;
func(&i);
}  

由于func_impl()function template旦被呼叫,编译器会自动进行 template
自变量推导。于是导出型别T迭代器相应型别( associated types)不只是「迭代器所指对象的型别」 种而已。根据经验,最常用的相应型别有五种, 

3.4 Traits 编程技法STL 源码门钥 

迭代器所指物件的型别,称为该迭代器的value type 函式的「 template 自变量推导机制」推而导之的只是自变量,无法推导函式的回返值型别。

可以声明“内嵌类型”

template <class T>
struct MyIter{
typedef Tvalue_type; // 内嵌型别宣告( nested type)
T* ptr;
MyIter(T* p=0) : ptr(p) { }
T& operator*() const { return *ptr; }
// ...
};
template <class I>
typename I::value_type //这一整行是 func的回返值型别
func(I ite)
{ return *ite; }
// ...
MyIter<int> ite(new int(8));
cout << func(ite); //输出: 8 

func()的回返型别必须加上关键词 typename, 关键词 typename的用意在告诉编译器说这是一个型别,如此才能顺利通过编译 。

但是有个隐晦的陷阱:并不是所有迭代器都是 class type。原生指标就不是!如果不是 class type,就无法为它定义内嵌型别。 但 STL(以及整个泛型思维)绝对必须接受原生指标做为一种迭代器,所以需要针对原生指标做特殊化处理。

可以使用(偏特化)template partial specialization「 针对(任何) template 参数更进一步的条件限制,所设计出来的一个特化版 」。换句话说我们可以在泛化设计中提供一个特化版版本 (也就是将泛化版中的某些template参数赋予明确的指定) 

先前的问题是,原生指标并非 class,因此无法为它们定义内嵌型别。现在,我们可以针对「迭代器之 template自变量为指」,设计特化版的迭代器。  

template <class I>
structiterator_traits { // traits 意为「特性」
typedef typename I::value_type value_type;
}; 

如果I定义有自己的value type,那么透过这个traits的作用,萃取出来的value_typ就是I::value_type。

那么先前那个func()可以改写成这样: 

template <class I>
typename iterator_traits<I>::value_type // 整行是函式回返型别
func(I ite)
{ return *ite; } 

但这除了多层间接性,又带来什么好处?好处是traits可以拥有特化版 。 如下

template <class T>
struct iterator_traits<T*> { //偏特化版—迭代器是个原生指标
typedefT value_type;
}; 

 如 果 迭 代 器 是 个pointer-to-const,我们应该设法令其value type为一个 non-const型别 

template <class T>
struct iterator_traits<const T*> { // 偏特化版—当迭代器是个pointer-to-const
typedefT value_type;
};
// 萃取出来的型别应该是 T 而非 const T 

现在,不论面对的是迭代器MyIter,或是原生指标int*或const int*,都可以透过traits取出正确的(我们所期望的) value type 

traits所扮演的「特性萃取机」角色,萃取各个迭代器的相应型别 若要这个「特性萃取机」 traits能够有效运作,每一个迭代器必须遵循约定,自行以内嵌型别定义( nested typedef)的方式定义出相应型别( associated types)。 

根据经验,最常用到的迭代器相应型别有五种: value type, difference type, pointer,
reference,iterator catagoly 

template <class I>
structiterator_traits {
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
}; 

3.4.1迭代器相应型别之一:value type 

所谓value type,是指迭代器所指对象的型别。任何一个打算与 STL算法有完美搭配的 迭代器class,都应该定义自己的 value type 内嵌型别, 

3.4.2迭代器相应型别之二:difference type 

difference type 用来表示两个迭代器之间的距离,也因此,它可以用来表示一个容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量。

针对相应型别difference typetraits的两个(针对原生指标而写的)特化版如下,以C++内建的ptrdiff_t( 定义于<cstddef>头文件)做为原生指标的difference type 

template <class I>
structiterator_traits {
...
typedef typename I::difference_type difference_type;
};
//针对原生指标而设计的「偏特化( partial specialization)」 版
template <class T>
structiterator_traits<T*> {
...
typedef ptrdiff_t difference_type;
};
//针对原生的 pointer-to-const 而设计的「偏特化( partial specialization)」 版
template <class T>
struct iterator_traits<const T*> { 

typedef ptrdiff_t difference_type;
}; 

现在,任何时候当我们需要任何迭代器 I的difference type,可以这么写:

typename iterator_traits<I>::difference_type 

3.4.3迭代器相应型别之三:reference type 

不允许改变「所指对象之内容」者,称为constant iterators,例如constint* pic;允许改变「所指对象之内容」者,称为 mutable iterators,例如int* pi。 当我们对一个 mutable iterators做解引用时,获得的应该是个值( lvalue) ,可以被赋值。

在 C++中,函如果要回左值,都是以by reference的方式进行, 所以当p是个 mutable iterators时,如果其value type是T,那么*p的型别不应该是T,应该是 T&。将此道理扩充,如果 p是一个 constant iterators,其value type是 T,那么*p的型别不应该是const T,而应该是const T&。 *p的型别,即所谓的reference type 

3.4.4迭代器相应型别之四:pointer type 

pointers和 references 在 C++中有非常密切的关连。 如果「传回一个左值,令它代表p所指之物」是可能的,那么「传回一个左值,令它代表p所指之物的位址」也一定可以。 我们能够传回一个 pointer,指向迭代器所指之物。 

这些相应型别已在先前的ListIter class中出现过: 

Item& operator*() const { return *ptr; }
Item* operator->() const { return ptr; }
Item&便是 ListIter的reference type而 Item*便是其pointer type

3.4.5迭代器相应型别之五:iterator_category  

根据移动特性与施行动作,迭代器被分为五类: 

Input Iterator:这种迭代器所指对象,不允许外界改变。只读( read only)。
cOutput Iterator:唯写( write only)。
cForward Iterator:允许「写入型」算法(例如replace())在此种迭代器所形成的区间上做读写动作。
cBidirectional Iterator:可双向移动。某些算法需要逆向走访某个迭代器区间(例如逆向拷贝某范围内的元素), 就可以使用 Bidirectional Iterators。
cRandom Access Iterator:前㆕ 种迭代器都只供应一部份指标算术能力(前三种支持operator++,第㆕ 种再加上operator--), 第五种则涵盖所有指标算术能力,包括p+n, p-n, p[n], p1-p2, p1<p2。 

这些迭代器的分类与从属关系

 

如果可能,我们尽量针对图中的某种迭代器提供一个明确定义,并针对更强化的某种迭代器提供另一种定义,这样才能在不同情况下提供最大效率。 

假设有个算法可接受Forward Iterator,你以Random Access Iterator喂给它,它当然也会接受,因为一个Random Access Iterator必然是一个ForwardIterator(见图 3-2)。 但是可用并不代表最佳! 

拿advance() 来说(这是许多算法内部常用的一个函式), 此函式有两个参数,迭代器p和数值n;函式内部将p累进n次(前进n距离)。 

template <class InputIterator, class Distance>
voidadvance(InputIterator& i, Distance n)
{
if (is_random_access_iterator(i))
advance_RAI(i, n);
//此函式有待设计
else if (is_bidirectional_iterator(i))//此函式有待设计
advance_BI(i, n);
else
advance_II(i, n);
} 

像这样在执行时期才决定使用哪一个版,会影响程序效率。最好能够在编译期就选择正确的版 。多载化函式机制可以达成这个目标。 

前述三个advance_xx()都有两个函式参数,型别都定(因为都是 template参数)。 为了令其同名,形成多载化函式,我们必须加上一个型别已确定的函式参数,使函式多载化机制得以有效运作起来。 

如果traits有能力萃取出迭代器的种类,我们便可利用这个「迭代器类型」相应型别做为advanced()的第三参数。这个相应型别一定必须是个class type,不能只是数值号码类的东西,因为编译器需仰赖它( 一个型别)来进行多载化决议程序( overloaded resolution)。  

上述语法,每个 __advance()的最后一个参数都只宣告型别,并指定参数名称,因为它纯粹只是用来启动多载化机制,函式之中根不使用该参数。 

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
__advance(i, n,
iterator_traits<InputIterator>::iterator_category());3
} 

任何一个迭代器,其类型永远应该落在「该迭代器所隶属之各种类型中,最强化的那个」。 例如int*既是Random Access Iterator又是 Bidirectional Iterator,同时也是Forward Iterator,而且也是Input Iterator,那么,其类型应该归属为random_access_iterator_tag 

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n);
按说advanced()既然可以接受各种类型的迭代器,就不应将其型别参数命名为InputIterator。这其实是 STL 算法的一个命名规则:以算法所能接受之最低阶迭代器类型,来为其迭代器型别参数命名。 

STL提供了一个iterators class如下,如果每个新设计的迭代器都继承自它,就保证符合 STL 所需之规范:
template <class Category,
class T,
class Distance = ptrdiff_t,
class Pointer = T*,
class Reference = T&>
structiterator {
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Referencereference;
};
iterator class不含任何成员,纯粹只是型别定义,所以继承它并不会招致任何额外负担。由于后三个参数皆有默认值,新的迭代器只需提供前两个参数即可。

土法炼钢的 ListIter,如果改用正式规格,应该这么写:
template <class Item>
struct ListIter :
public std::iterator<std::forward_iterator_tag, Item>
{ ... }  

总结
设计适当的相应型别( associated types), 是迭代器的责任。设计适当的迭代器,
则是容器的责任。唯容器ᴀ身,才知道该设计出怎样的迭代器来走访自己,并执行迭代器该有的各种行为(前进、后退、取值、取用成员…)。 至于算法,完全可以独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行 

traits编程技法,大量运用于 STL 实作品中。它利用「巢状型别」的写码技巧与
编译器的template自变量推导功能,补强 C++᳾能提供的关于型别认证方面的能
力,补强 C++不为强型( strong typed)语言的遗憾。 

 

3.7 SGI STL 的私房菜:__type_traits 

traits编程技法很棒,适度弥补了 C++ 语言ᴀ身的不足。 STL只对迭代器加以规范,制定出iterator_traits这样的东西。 SGI 把这种技法进一步扩大到迭代器以外的世界,于是有了所谓的__type_traits。 

iterator_traits负 责萃 取 迭 代器 的特 性, __type_traits 则 负责 萃取 型 别 ( type)的特性。 型别特性是指:这个型别是否具备non-trivialdefalt ctor ?是否具备 non-trivial copy ctor?是否具备 non-trivialassignmentoperator?是否具备 non-trivialdtor?如果答案是否定的,我们在对这个型别进行建构、解构、拷贝、赋值等动作时,就可以采用最有效率的措施(例如根不唤起身居高位,不谋其职的那些constructor, destructor), 而采用内存直接处理动作 如malloc()、 memcpy()等等,获得最高效率。这对于大规模而动作频繁的容器,有着显著的效率提升

定义于 SGI <type_traits.h>中的__type_traits,提供了一种机制,允许针对不同的型别属性( type attributes), 在编译时期完成函式派送决定( functiondispatch)。 这对于撰写 template很有帮助,例如,当我们准备对一个「元素型别知」的数组执行 copy 动作时,如果我们能事先知道其元素型别是否有一个 trivialcopy constructor , 便 能 够 帮 助 我 们 决 定 是 否 可 使 用 快 速 的memcpy()或memmove()。

template <class type>
struct__type_traits{
typedef __true_type this_dummy_member_must_be_first;

typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
}; 

SGI 定义出最保守的值,然后(稍后可见)再针对每一个纯量型别( scalar types)设计适当的__type_traits特化版ᴀ ,这样就解决了问题。  

/*以下针对 C++基ᴀ型别 char, signed char, unsigned char, short,unsigned short, int, unsigned int, long, unsigned long, float, double,long double 提供特化版ᴀ 。注意,每一个成员的值都是 __true_type,表示这些型别都可采用最快速方式(例如 memcpy)来进行拷贝( copy)或赋值( assign)动作。 */ 

__STL_TEMPLATE_NULL struct __type_traits<char> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
__STL_TEMPLATE_NULL struct __type_traits<signed char> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
__STL_TEMPLATE_NULL struct __type_traits<unsigned char> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
__STL_TEMPLATE_NULL struct __type_traits<short> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
__STL_TEMPLATE_NULL struct __type_traits<unsigned short> {
typedef __true_type has_trivial_default_constructor; 

。。。

。。。

注意,针对原生指标设计 __type_traits 偏特化版 。原生指标亦被视为种纯量型别。
template <class T>
struct__type_traits<T*> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
}; 

假设我自行定义了一个Shape class, __type_traits 针对 Shape 萃取出来的每一个特性都是__false_type ,即使Shape是个 POD型别。这样的结果当然过于保守,但是别无选择,除非我针对 Shape,自行设计一个__type_traits 特化版本。

template<>struct __type_traits<Shape> {
typedef __true_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
}; 

究竟一个 class什么时候该有自己的 non-trivial default constructor, non-trivial copyconstructor, non-trivial assignment operator, non-trivial destructor 呢? 一个简单的判断准则是: 如果 class 内含指标成员,并且对它进行内存动态配置,那么这个class就需要实作出自己的 non-trivial-xxx5 

至少,有了这个__type_traits之后,当我们设计新的泛型算法时,面对C++纯量型别,便有足够的信息决定采用最有效的拷贝动作或赋值动作—因为每一个纯量型别都有对应的__type_traits 特化版 ,其中每一个 typedef 的值都是__true_type。 

猜你喜欢

转载自blog.csdn.net/qq_19525389/article/details/81428388