STL:空间配置器(allocator)和迭代器(iterator)

空间配置器(allocator)


在 STL 中,内存配置操作由 alloc:allocate() 负责,内存释放由 alloc:deallocate() 负责;对象构造由 ::construct() 负责,对象析构操作由 ::destroy() 负责。

STL中配置器定义于 memory 头文件之中,SGI 内包含以下两个文件:
#include <stl_alloc.h> // 负责内存空间的配置与释放
#include <stl_construct.h> // 负责对象内容的构造与析构memory

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 剖析

free-list

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 异常。
memory pool

// 内存池,从内存池中取空间给 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 Iclass 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));
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一份讲解全面的标准模板库STL学习资料 标准模板库STL主要由6大组件组成: (1)容(Containers)。包括各种基本数据结构的类模板。 STL部分主要由头文件<vector>、<list>、<deque>、<set>、< map>、<stack>和<queue>组成。 (2)算法(Algorithms)。包括各种基本算法,如比较、交换、查找、排序、遍历操作、复制、修改、移除、反转、合并等等。 STL算法部分主要由头文件<algorithm>和<numeric>组成。 (3)迭代器Iterators)。迭代器是面向对象版本的指针,如同指针可以指向内存中的一个地址,迭代器可以指向容中的一个位置。 STL的每一个容类模板中,都定义了一组对应的迭代器类,用以存取容中的元素。这样,在STL迭代器就将算法和容联系起来了,通过迭代器算法函数可以访问容中指定位置的元素,而无需关心元素的具体类型。 STL迭代器部分主要由头文件<utility>和<iterator>组成。 (4)函数对象(Function Objects)。一种行为类似于函数的class,实现技术上是一个改写了“call operator()”的class。 STL 提供 15 个预定义的 Function objects。头文件<functional>中定义了一些类模板,用以声明函数对象。 (5)适配(Adaptors)。简单地说就是一种接口类,专门用来修改现有类的接口,提供一种新的接口;或调用现有的函数来实现所需要的功能。 主要包括3种适配Container Adaptors、Iterator Adaptors与Function Adaptors。其中迭代器适配的定义在头文件<iterator>中,函数适配的定义在头文件<functional>中。 (6)内存配置Allocators)。为STL提供空间配置的系统。 头文件<memory>中的主要部分是模板类allocator,它负责产生所有容中的默认分配。容使用allocator完成对内存的操作,allocator提供内存原语以对内存进行统一的存取。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值