空间配置器(allocator)
在 STL 中,内存配置操作由 alloc:allocate() 负责,内存释放由 alloc:deallocate() 负责;对象构造由 ::construct() 负责,对象析构操作由 ::destroy() 负责。
STL中配置器定义于 memory 头文件之中,SGI 内包含以下两个文件:
#include <stl_alloc.h> // 负责内存空间的配置与释放
#include <stl_construct.h> // 负责对象内容的构造与析构
1. 构造和析构的基本工具:construct() 和 destroy()
#include <new.h>
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value); // placement new; 调用 T1::T1(value),将初值设定到指针所指的空间上。
}
// 以下是 destroy() 第一版本,接受一个指针
template <class T>
inline void destroy(T* pointer) {
pointer->~T(); // 调用 dtor ~T()
}
// 以下是 destory() 第二版本,接受两个迭代器。此函数设法找出元素的数值型别
// 进而利用 _type_traits<> 求取最适当措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
_destroy(first, last, value_type(first));
}
// 判断元素的数值型别 是否有 trivial destructor
template <class ForwardIterator, class T>
inline void _destroy(ForwardIterator first, ForwardIterator last, T*) {
typedef typename _type_traits<T>::has_trivial_destructor trivial_destructor;
_destroy_aux(first, last, trivial_destructor());
}
// 如果元素的数值型别 有 non-trivial destructor...
template <class ForwardIterator>
inline void _destroy_aux(ForwardIterator first, ForwardIterator last, _false_type) {
for( ; first < last; ++first)
destroy(&*first);
}
// 如果元素的数值型别 有 trivial destructor...
template <class ForwardIterator>
inline void _destroy_aux(ForwardIterator, ForwardIterator, _true_type) {}
2. 空间的配置与释放:std::alloc
对象构造前的空间配置和对象析构后的空间释放,由 <stl_alloc.h> 负责,SGI 对此的设计哲学如下:
- 向 system heap 要求空间。
- 考虑多线程(multi-threads) 状态。
- 考虑内存不足时的应变措施。
- 考虑过多“小型区块” 可能造成的内存碎片(fragment)问题。
C++ 的内存配置基本操作是 ::operator new() ,内存释放基本操作是 ::operator delete()。这两个全局函数相当于 C 的 malloc() 和 free() 函数。
考虑到小型区块所可能造成的内存破碎问题,SGI 设计了双层级配置器,第一级配置器直接使用 malloc() 和 free() ,第二级配置器则视情况采用不同的策略:当配置区块超过 128bytes 时,视之为“足够大”,便调用第一级配置器;当配置区块小于 128bytes 时,视之为“过小”,为了降低额外负担,便采用复杂的 memory pool 整理方式,而不再求助于第一级配置器。整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于 _USE_MALLOC 是否被定义:
#ifdef _USE_MALLOC
...
typedef _malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc; // 令 alloc 为第一级配置器
#else
// 令 alloc 为第二级配置器
typedef _default_alloc_template<_NODE_ALLOCATOR_THREADS, 0> alloc;
#endif /* ! _USE_MALLOC */
其中 _malloc_alloc_template 就是第一级配置器, _default_alloc_template 就是第二级配置器。
无论 alloc 被定义为第一级或第二级配置器,SGI 还为它再包装一个接口,使配置器的接口更符合 STL 规格:
template <class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n) {
return 0 == n ? 0 : (T*) Alloc::allocate(n * sizeof(T));
}
static T * allocate(void) {
return (T*) Alloc::allocate(sizeof(T));
}
static void deallocate(T *p, size_t n) {
if(0 != n) Alloc::deallocate(p, n * sizeof(T));
}
static void deallocate(T *p) {
Alloc::deallocate(p, sizeof(T));
}
};
其内部四个成员函数其实都是单纯的转调用,调用传递给配置器(可能是第一级也可能是第二级)的成员函数。这个接口使配置器的配置单位从 bytes 转为个别元素的大小(sizeof(T))。SGI STL 容器全都使用这个 simple_alloc 接口。
2.1 第一级配置器 __malloc_alloc_template 剖析
#if 0
# include <new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream>
# define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)
#endif
template <int inst>
class __malloc_alloc_template {
private:
// 以下都是函数指针,所代表的函数用来处理内存不足的情况
// oom : out of memory
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
static void (* __malloc_alloc_oom_handler) ();
public:
static void * allocate(size_t n) {
void *result = malloc(n); // 第一级配置器直接使用 malloc()
// 以下无法满足需求时,改用 oom_malloc()
if(0 == result) result = oom_malloc(n);
return result;
}
static void deallocate(void *p, size_t /* n */) {
free(p); // 第一级配置器直接使用 free()
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) {
void * result = realloc(p, new_sz); // 第一级配置器直接使用 realloc()
// 以下无法满足需求时,改用 oom_realloc()
if(0 == result) result = oom_realloc(p, new_sz);
return result;
}
// 以下仿真 C++ 的 set_new_handler(). 换句话说,你可以通过它
// 指定你自己的 out-of-memory handler
static void (* set_malloc_hander(void (*f)))() {
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
// malloc_alloc out-of-memory handling
// 初值为 0. 有待客端设定
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {
void (* my_malloc_handler)();
void *result;
for( ; ;) { // 不断尝试释放、配置、再释放、再配置....
my_malloc_handler = __malloc_alloc_oom_handler;
if(0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(* my_malloc_handler)(); // 调用处理例程,企图释放内存
result = malloc(n); // 再次尝试配置内存
if(result) return(result);
}
}
template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) {
void (* my_malloc_handler)();
void *result;
for( ; ; ) { // 不断尝试释放、配置、再释放、再配置
my_malloc_handler = __malloc_alloc_oom_handler;
if(0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)(); // 调用处理例程,企图释放内存
reuslt = realloc(p, n); // 再次尝试配置内存
if(result) return(result);
}
}
// 注意,以下直接将参数 inst 指定为 0
typedef __malloc_alloc_template<0> malloc_alloc;
所谓 C++ new handler 机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。一旦 ::operator new 无法完成任务,在丢出 std::bad_alloc 异常状态之前,会先调用由客端指定的处理例程。该处理例程通常即被称为 new-handler。new-handler 解决内存不足的做法有特定的模式。
2.2 第二级配置器 __default_alloc_template 剖析
enum {__ALIGN = 8}; // 小型区块的上调边界
enum {__MAX_BYTES = 128}; // 小型区块的上限
enum {__NFREELISTS = __MAX_BYTES / __ALIGN}; // free-list 个数
// 以下是第二级配置器
// 注意,无 “template 型别参数”,且第二参数完全没派上用场
template <bool threads, int inst>
class __default_alloc_template{
private:
// ROUND_UP 将 bytes 上调至 8 的倍数
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
private:
union obj { // free-list 的节点构造
union obj * free_list_link;
char client_data[1];
};
private:
// 16 个 free-list
static obj * volatile free_list[__NFREELISTS];
// 以下函数根据区块大小,决定使用第 n 号 free-list。n 从 1 起算
static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN) / __ALIGN - 1);
}
// 返回一个大小为 n 的对象,并可能加入大小为 n 的其它区块到 free-list
static void *refill(size_t n);
// 配置一大块空间,可容纳 nobjs 个大小为 “size” 的区块
// 如果配置 nobjs 个区块有所不便, nobjs 可能会降低
static char *chunk_alloc(size_t size, int &nobjs);
static char *start_free; // 内存池起始位置。只在chunk_alloc() 中变化
static char *end_free; // 内存池结束位置,只在chunk_alloc() 中变化
static size_t heap_size;
public:
static void * allocate(size_t n) { }
static void deallocate(void *p, size_t n) { }
static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};
// 以下是 static data member 的定义与初值设定
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;
template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
2.3 空间配置函数:allocate()
身为一个配置器,__default_alloc_template 拥有配置器的标准接口函数 allocate()。此函数首先判断区块大小,大于 128bytes 就调用第一级配置器,小于 128bytes 就检查对应的 free list。如果 free list 之内有可用的区块,就直接拿来用,如果没有可用区块,就将区块大小上调至 8 倍数边界,然后调用 refill(),准备为 free list 重新填充空间。
static void * allocate(size_t n) {
obj * volatile * my_free_list;
obj * result;
// 大于 128 就调用第一级配置器
if(n > (size_t)__MAX_BYTES) {
return(malloc_alloc::allocate(n));
}
// 寻找 16 个 free list 中适当的一个
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if(result == 0) {
// 没找到可用的 free list ,准备重新填充 free list
void *r = refill(ROUND_UP(n));
return r;
}
// 调整 free list
*my_free_list = result -> free_list_link;
return(result);
}
static void deallocate(void *p, size_t n) {
obj *q = (obj *)p;
obj * volatile * my_free_list;
//大于 128 就调用第一级配置器
if(n > (size_t)__MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}
// 寻找对应的 free list
my_free_list = free_list + FREELIST_INDEX(n);
//调整 free list, 回收区块
q -> free_list_link = *my_free_list;
*my_free_list = q;
}
// refill() 函数
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n) {
int nobjs = 20;
// 调用chunk_alloc() 尝试取得 nobjs 个区块作为 free list 的新节点
// 注意参数 nobjs 是 pass by reference
char * chunk = chunk_alloc(n, nobjs);
obj * volatile * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;
// 如果只获得一个区块,这个区块就分配给调用者用,free list 无新节点
if(1 == nobjs) return(chunk);
// 否则准备调整 free list, 纳入新节点
my_free_list = free_list + FREELIST_INDEX(n);
// 以下在 chunk 空间建立 free list
result = (obj*)chunk; // 这一块准备返回给客端
// 以下导引 free list 指向新配置的空间(取自内存池)
*my_free_list = next_obj = (obj*)(chunk + n);
// 以下将 free list 的各节点串联起来
for(i=1; ; i++) {
current_obj = next_obj;
next_obj = (obj*)((char *)next_obj + n);
if(nobjs - 1 == i) {
current_obj -> free_list_link = 0;
break;
}else {
current_obj -> free_list_link = next_obj;
}
}
return(result);
}
2.4 内存池(memory pool)
上述的 chunk_alloc() 函数以end_free - start_free 来判断内存池的水量。如果水量充足,就直接调出20个区块返回给 free list。如果水量不足以提供20个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。这时候其 pass by reference 的 nobjs 参数将被修改为实际能够供应的区块数。如果内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用 malloc() 从 heap 中配置内存,为内存池注入活水源头以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
举个例子,假设程序一开始,客端就调用 chunk_alloc(32,20),于是 malloc() 配置 40 个 32bytes 区块,其中第1个交出,另19个交给free_list[3] 维护,余20个留给内存池。接下来客端调用 chunk_alloc(64,20),此时 free_list[7] 空空如也,必须向内存池要求支持。内存池只够供应 (32*20)/64=10个64bytes 区块,就把这10个区块返回,第1个交给客端,余9个由 free_list[7] 维护。此时内存池全空,接下来再调用 chunk_alloc(96,20),此时 free_list[11]空空如也,必须向内存池要求支持,而内存池此时也是空的,于是以 malloc() 配置40+N(附加量)个96字节区块,其中第1个交出,另19个交给 free_list[11] 维护,余20+N(附加量)个区块留给内存池。
万一山穷水尽,整个系统堆空间都不够了(以至无法为内存池注人活水源头),malloc() 行动失败,chunk_alloc() 就四处寻找有无“尚有未用区块,且区块足够大"之 free list。找到了就挖一块交出来,找不到就调用第一级配置器,第一级配置器其实也是使用 malloc() 来配置内存,但它有 out-of-memory处理机制(类似 new-handler 机制),或许有机会释放其它的内存拿来此处使用。不成功就抛出 bad_alloc 异常。
// 内存池,从内存池中取空间给 free list 使用,是 chunk_alloc() 的工作:
// 假设 size 已经适当上调至 8 的倍数
// 注意参数 nobjs 是 pass by reference
template <bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) {
char * result;
size_t total_bytes = size * nobjs;
size_t bytes_left = end_free - start_free; // 内存池剩余空间
if(bytes_left >= total_bytes) {
// 内存池剩余空间完全满足需求
result = start_free;
start_free += total_bytes;
return(result);
}else if(bytes_left >= size) {
// 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块
nobjs = bytes_left / size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
}else {
// 内存池剩余空间连一个区块的大小都无法供应
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
// 以下试着让内存池中的残余零头还有利用价值
if(bytes_left > 0) {
// 内存池内还有一些零头,先配给适当的 free list
// 首先寻找适当的 free list
obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
// 调整 free list,将内存池中的残余空间编入
((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}
// 配置 heap 空间,用来补充内存池
start_free = (char *)malloc(bytes_to_get);
if(0 == start_free) {
// heap 空间不足, malloc() 失败
int i;
obj * volatile * my_free_list, *p;
// 以下搜寻 “尚有未用区块,且区块够大” 之free list
for(i = size; i <= __MAX_BYTES; i+= __ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if(0 != p) {
*my_free_list = p -> free_list_link;
start_free = (char *)p;
end_free = start_free + i;
// 递归调用自己,为了修正 nobjs
return(chunk_alloc(size, nobjs));
//注意,任何残余零头终将被编入适当的 free-list 中备用
}
}
end_free = 0; // 如果出现意外,到处都没有内存可用了
// 调用第一级配置器,看看 out-of-memory 机制能否尽点力
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
//这回导致抛出异常,或内存不足的情况获得改善
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
// 递归调用自己,为了修正 nobjs
return(chunk_alloc(size, nobjs));
}
}
3. 内存基本处理工具
3.1 uninitialized_copy()
此函数使我们能够将内存的配置与对象的析构行为分离开来。如果作为输出目的地的 [result,result + (last - fitst)] 范围内的每一个迭代器都指向未初始化区域,则 uninitialized_copy() 会使用 copy constructor,给身为输入来源 [first,last) 范围内的每一个对象产生一份复制品,放进输出范围中。
// uninitialized_copy() 函数
// 迭代器 first 指向输入端的起始位置
// 迭代器 last 指向输入端的结束位置(前闭后开区间)
// 迭代器 result 指向输出端(欲初始化空间)的起始处
template <class InputIterator, class ForwardIterator>
inline ForwardIterator uninitialized_copy(InputIterator first, ForwardIterator last, ForwardIterator result) {
return __uninitialized_copy(first, last, result, value_type(result));
}
template <class InputIterator, class ForwardIterator>
inline ForwardIterator __uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result, T*) {
typedef typename __type_traits<T>::is_POD_type is_POD;
return __uninitialized_copy_aux(first, last, result, is_POD());
}
template <class InputIterator, class ForwardIterator>
inline ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, __true_type) {
return copy(first, last, result); // 调用 STL 算法 copy()
}
template <class InputIterator, class ForwardIterator>
ForwardIterator __uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, __false_type) {
ForwardIterator cur = result;
for( ; first != last; ++first, ++cur)
construct(&*cur, *first);
return cur;
}
// 针对 char* 和 wchar_t* 两种型别,可以采用最具效率的 memmove(直接移动内存内容)来执行复制行为
// 以下针对 const char* 的特化版本
inline char* uninitialized_copy(const char* first, const char* last, char* result) {
memmove(result, first, last - first);
return result + (last - first);
}
// 以下针对 const wchar_t* 的特化版本
inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_t* last, wchar_t* result) {
memmove(result, first, sizeof(wchar_t) * (last - first));
return result + (last - first);
}
3.2 uninitialized_fill()
此函数也能够使我们将内存配置与对象的构造行为分离开来。如果 [first,last)范围内的每个迭代器都指向未初始化的内存,那么 uninitialized_fill() 会在该范围内产生 x 的复制品。
// uninitialized_fill()
// 迭代器 first 指向输出端(欲初始化空间)的起始处
// 迭代器 last 指向输出端(欲初始化空间)的结束处(前闭后开区间)
// x 表示初值
template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x) {
__uninitialized_fill(first, last, x, value_type(first));
}
template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x, T1*) {
typedef typename __type_traits<T1>::is_POD_type is_POD;
__uninitialized_fill_aux(first, last, x, is_POD());
}
template <class ForwardIterator, class T>
inline void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __true_type) {
fill(first, last, x); // 调用 STL 算法 fill()
}
template <class ForwardIterator, class T>
void __uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, __false_type) {
ForwardIterator cur = first;
for( ; cur != last; ++cur)
construct(&*cur, x); // 必须一个一个元素地构造,无法批量进行
}
3.3 uninitialized_fill_n()
此函数也能够使我们将内存配置与对象的构造行为分离开来。它会为指定范围内的所有元素设定相同的初值。
如果 [first,last)范围内的每个迭代器都指向未初始化的内存,那么 uninitialized_fill_n() 会调用 copy constructor,在该范围内产生 x 的复制品。
// uninitialized_fill_n() 本函数接受三个参数
// 迭代器 first 指向欲初始化空间的起始处
// n 表示欲初始化空间的大小
// x 表示初值
template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x) {
return __uninitialized_fill_n(first, n, x, value_type(first));
// 以上,利用 value_type() 取出 first 的 value type
}
// 这个函数的逻辑是,首先萃取出迭代器 first 的 value type,然后判断该型别是否为 POD 型别
template <class ForWardIterator, class Size, class T, class T1>
inline ForWardIterator __uninitialized_fill_n(ForWardIterator first, Size n, const T& x, T1*) {
typedef typename __type_traits<T1>::is_POD_type is_POD;
return __uninitialized_fill_n_aux(first, n, x, is_POD());
}
// POD 指 Plain Old Data,也就是标量型别或传统的 C struct 型别。
template <class ForwardIterator, class Size, class T>
inline ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, __true_type) {
return fill_n(first, n, x); // 交由高阶函数执行
}
// 如果不是 POD 型别,执行流程就会转进以下函数。这是由 function template 的参数推导机制而得
template <class ForwardIterator, class Size, class T>
ForwardIterator __uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, __false_type) {
ForwardIterator cur = first;
for( ; n > 0; --n, ++cur)
construct(&*cur, x);
return cur;
}
迭代器(iterator)
1. 偏特化
我们可以在泛化设计中提供一个特化版本(也就是将泛化版本中的某些 template 参数赋予明确的指定)。针对任何 template 参数更进一步的条件限制所设计出来的一个特化版本。
template <typename T>
class C {...} // 这个泛化版本允许(接受)T 为任何型别
template <typename T>
class C<T*> {...} // 这个特化版本适用于 “T 为原生指针” 的情况
// T 为原生指针便是 T 为任何型别的一个更进一步的条件限制
2. 迭代器的型别
2.1 value type
所谓 value type ,是指迭代器所指对象的型别。任何一个打算于 STL 算法有完美搭配的 class,都应该定义自己的 value type 内嵌型别。
2.2 difference type
difference type 用来表示两个迭代器之间的距离,因此它也可以用来表示一个容器的最大容量,因为对于连续空间的容器而言,头尾之间的距离就是其最大容量。如果一个泛型算法提供计数功能,例如 STL 的 count() ,其传回值就必须使用迭代器的 difference type:
template <class I, class T>
typename iterator_traits<I>::difference_type // 这一整行是函数返回型别
count(I first, I last, count T& value) {
typename iterator_traits<I>::difference_type n = 0;
for( ; first != last; ++first){
if(*first == value)
++n;
}
return n;
}
2.3 reference type
从 “迭代器所指之物的内容是否允许改变”的角度观之,迭代器分为两种:不允许改变“所指对象之内容”者,称为 constant iterators, 例如 const int* pic;允许改变 “所指对象之内容” 者,称为 mutable iterators,例如 int* pi。
在 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。
2.4 pointer type
如果 “传回一个左值,令它代表 p 所指之物” 是可能的,那么 “传回一个左值,令它代表 p 所指之物的地址” 也一定可以。也就是说,我们能够传回一个 pointer,指向迭代器所指之物。
2.5 iterator_category
根据移动特性于施行操作,迭代器被分为五类:
- Input Iterator: 这种迭代器所指对象,不允许外界改变。只读(read only)
- Output Iterator: 唯写(write only)
- Forward Iterator: 允许 ”写入型“ 算法(例如:replace() )在此种迭代器所形成的区间上进行读写操作
- Bidirectional Iterator: 可双向移动。某些算法需要逆向走访某个迭代器区间(例如逆向拷贝某范围内的元素)
- Random Access Iterator: 前四种迭代器都只供应一部分指针算数能力(前三种支持 operator++,第四种再加上 operator–),第五种则涵盖所有指针算数能力,包括 p+n,p-n,p[n],p1-p2,p1<p2。
拿 advance() 来说(这是许多算法内部常用的一个函数),该函数有两个参数,迭代器 p 和数值 n;函数内部将 p 累进 n 次(前进 n 距离)。
template <class InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n) {
// 单向,逐一前进
while(n--) ++i;
}
template <class BidirectionalIterator, class Distance>
void advance_BI(BidirectionalIterator& i, Distance n) {
// 双向,逐一前进
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
template <class RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n) {
// 双向,跳跃前进
i += n;
}
现在,当程序调用 advance() 时,应该选用哪一份函数定义呢?如果选择 advance_II(),对Random Access Iterator 而言极度缺乏效率,原本 O(1) 的操作竟成为 O(n)。如果选择 advance_RAI(),则它无法接受 Input Iterator。我们需要将三者合一:
template <class InputIterator, class Distance>
void advance(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() 都有两个函数参数,型别都未定。为了令其同名,形成重载函数,我们必须加上一个型别已确定的函数参数,使函数重载机制有效的运作起来。
如果 traits 有能力萃取出迭代器的种类,我们便可利用这个 ”迭代器类型“ 相应型别作为 advance() 的第三参数。这个相应型别一定必须是一个 class type,不能只是数值号码类的东西,因为编译器需仰赖它来进行重新决议。
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag { };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
// 单向,逐一前进
while(n--) ++i;
}
template <class ForwardIterator, class Distance>
inline void __advance(ForwardIterator& i, Distance n, forward_iterator_tag) {
// 单纯地进行传递调用
advance(i, n, __cpp_init_captures());
}
template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) {
// 双向,逐一前进
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) {
// 双向,跳跃前进
i += n;
}
// 每个 __advance() 最后一个参数都只声明型别,并未指定参数名称,以为它纯粹只是个用来激活重载机制,函数之中
// 根本不使用该参数
3.iterator 源码
设计适当的相应型别,是迭代器的责任。设计适当的迭代器,则是容器的责任。唯容器本身,才知道该设计出怎样的迭代器遍历自己,并执行迭代器该有的各种行为。
#include <cstddef>
// 五种迭代器类型
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
template <class Category, class T, class Distance = ptrdiff_t, class Pointer = T*, class Reference = T&>
struct iterator {
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
};
// 榨汁机 traits
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
// 针对原生指针而设计的 traits 偏特化版
template <class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
// 针对原生之 pointer-to-const 而设计的 traits 偏特化版
template <class T>
struct iterator_traits<const T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
// 这个函数可以很方便地决定某个迭代器的类型(category)
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
typedef typename iterator_traits<Iterator>::iterator_category category;
return category(); // 型别是个对象
}
// 这个函数可以很方便地决定某个迭代器的 distance type
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&) {
return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}
// 这个函数可以很方便地决定某个迭代器的 value type
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&) {
return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}
// 以下是整组 distance 函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
__distance(InputIterator first, InputIterator last, input_iterator_tag) {
iterator_traits<InputIterator>::difference_type n = 0;
while(first != last) {
++first; ++n;
}
return n;
}
template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag) {
return last - first;
}
// 接口函数,只需要传入两个参数,根据类型,决定调用哪一个函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {
typedef typename iterator_traits<InputIterator>::iterator_category category;
return __distance(first, last, category());
}
// 以下是整租 advance 函数
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n, input_iterator_tag) {
while(n--) ++i;
}
template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) {
if(n >= 0)
while(n--) ++i;
else
while(n++) --i;
}
template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) {
i += n;
}
// 接口函数
template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
__advance(i, n, iterator_category(i));
}