STL 剖析

发布于:2024-12-18 ⋅ 阅读:(9) ⋅ 点赞:(0)

STL 六大组件

「STL 六大组件的交互关系」

配置器(allocator)

配置器:负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的 class template。

空间配置器:整个 STL 的操作对象(所有的数值)都存放在容器之内,而容器一定需要配置空间以存放内容。

具有次配置力(sub-allocation)的 SGI 空间配置器

SGI STL 空间配置器的结构

SGI STL 的配置器,其名称是 alloc 而不是 allocator,而且不接受任何参数。

SGI STL 的每一个容器都已经指定其缺省的空间配置器为 alloc。

template <class T, class Alloc = alloc>  // 缺省使用 alloc 为配置器

class vector {...};



vector<int, std::alloc> iv;

  • <defalloc.h>----SGI 标准的空间配置器,std::allocator

allocator 只是基层内存配置/释放行为(::operator::new 和 ::operator::delete)的一层薄薄的包装,并没有考虑到任何效率上的强化。

  • SGI 特殊的空间配置器,std::alloc
    • <stl_construct.h>:定义了全局函数 construct() 和 destroy(),负责对象的构造和析构。
    • <stl_alloc.h>:定义了一、二级配置器,配置器名为 alloc。
    • <stl_uninitialized.h>:定义了全局函数,用来填充(fill)或复制(copy)大块内存数据。
  • 构造和析构基本工具

具体看 <stl_construct.h> 源码,功能是构造和析构操作。

  • 空间的配置和释放,std::alloc
    • 向 system heap 要求空间
    • 考虑多线程(multi-threads)状态
    • 考虑内存不足时的应变措施
    • 考虑过多 “小型区块” 可能造成的内存碎片问题

对象构造前的空间配置 和 对象析构后的空间释放,具体看 <stl_alloc.h>。

SGI STL 空间配置器的分析

考虑到小型区块可能造成内存碎片问题,SGI 采用两级配置器,第一级配置器直接使用 malloc() 和 free() 实现;第二级配置器使用 memory pool 内存池管理。

第二级配置器的原理:

  • 当配置区块超过 128 bytes,就使用第一级配置器
  • 当配置区块小于 128 bytes,使用内存池管理

enum {_ALIGN = 8};  // 小型区块的上调边界

enum {_MAX_BYTES = 128}; // 小区区块的上限

enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN  free-list 的个数

// free-list 的节点结构,降低维护链表 list 带来的额外负担

union _Obj {

    union _Obj* _M_free_list_link;  // 利用联合体特点

    char _M_client_data[1];    /* The client sees this. */

};

static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];  // 注意,它是数组,每个数组元素包含若干相等的小额区块

其中 free-list 是指针数组,16 个数组元素,就是 16 个 free-list,各自管理大小分别为 8, 16, 24, 32,...128 bytes(8 的倍数)的小额区块。

小额区块的结构体 union _Obj 使用链表连接起来。

配置器负责配置,同时也负责回收。

迭代器(iterator)

迭代器:扮演容器与算法之间的桥梁,是所谓的 “泛型指针”,共有五种类型,以及其它衍生变化。从实现的角度来看,迭代器是一种将 operator*,operator->,operator++,operator-- 等指针相关操作予以重载的 class template。 所有 STL 容器都附带有自己专属的迭代器。 native pointer 也是一种迭代器。

迭代器(iterator) 是一种 smart pointer

迭代器是一种行为类似指针的对象,而指针的各种行为中最常见的用途是 dereference 和 member access。迭代器最重要的就是对 operator* 和 operator->进行重载工作。

auto_ptr:用来包装原生指针(native pointer)的对象,在头文件 中定义。

为什么每一种 STL 容器都提供有专属迭代器的缘故。

主要是暴露太多细节,所以把迭代器的开发工作交给容器去完成,这样所有实现细节可以得到封装,不被使用者看到。

迭代器相应类型(associated types)

迭代器所指对象的类型。

利用 function template 的参数推导机制,只能推导出参数的类型,无法推导出函数返回值类型。

迭代器相应类型有五种:

  • value type
  • difference type
  • pointer
  • reference
  • iterator category

Traits 编程技术

traits 意为 “特性”,扮演 “特性萃取机” 角色,萃取各个迭代器的特性(相应类型)。

template partial specialization 模板偏特化:针对 template 参数更进一步的条件限制所设计出来的一个特化版本,本身仍然是 template。

tempalte<typename I>

struct iterator_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;

};
  • 迭代器相应类型之一:value type

value type 就是迭代器所指对象的类型。

template <class T>

typename iterator_traits<I>::value_type func(I ite)

{

    return *ite;

}
  • 迭代器相应类型之二:difference type

difference type 用来表示两个迭代器之间的距离。

template <class I, class T>

typename iterator_traits<I>::difference_type cout(I first, I last, const T& value)

{

    typename iterator_traits<I>::difference_type n = 0;

    for (; first != last; ++first)

    {

        ++n;

    }

   

    return n;

}
  • 迭代器相应类型之三:reference type

在 c++ 中,函数如果要传回左值,都是以 by reference 的方式进行,所以如果 p 是一个迭代器,它的 value type 是 T ,那么*p 应该是T& (即reference typ·e)

  • 迭代器相应类型之四:pointer type
    •      迭代器相应类型之五:iterator_category
      • 输入迭代器 (InputIterator) 是能从所指向元素读取的迭代器 (Iterator) 。输入迭代器 (InputIterator) 仅保证单趟算法的合法性。
      • 输出迭代器 (OutputIterator) 是能写入所指元素的迭代器 (Iterator) 。
      • 向前迭代器 (ForwardIterator) 是一种能从所指向元素读取数据的迭代器 (Iterator) 。
      • 双向迭代器 (BidirectionalIterator) 是能双向移动(即自增与自减)的向前迭代器 (ForwardIterator) 。
      • 随机访问迭代器 (RandomAccessIterator) 是能在常数时间内移动到指向任何元素的双向迭代器 (BidirectionalIterator) 。

总结

traits 本质是什么?

多一层间接性,换来灵活性。

iterator_traits 负责萃取迭代器的特性,__type_traits 负责萃取类型的特性

容器(container)

容器:包括序列式容器和关联式容器;即各种数据结构,如 vector,list,deque,set,map 等用来存储数据;从实现的角度来看,STL 容器是一种 class template。

任何特定的数据结构都是为了实现某种特定的算法。

序列式容器(sequence container)

  • array (C++ 提供,build-in)
  • vector
  • heap (内含一个 vector)
  • priority-queue (内含一个 heap)
  • list
  • slist (非标准)
  • deque
  • stack (内含一个 deque) (adapter 配接器)
  • queue (内含一个 deque) (adapter 配接器)

怎么理解序列式容器,其中的元素都可序(ordered), 但未必有序(sorted)?

ordered 是容器集合被排序,可以使用指定的顺序去遍历集合。 sorted 是一个容器集合根据某些规则确定排序的。

关联式容器(associative container)

  • RB-tree (非公开)
  • set (内含一个 RB-tree)
  • map (内含一个 RB-tree)
  • multiset (内含一个 RB-tree)
  • multimap (内含一个 RB-tree)
  • hashtable (非标准)
  • hash_set (内含一个 hashtable) (非标准)
  • hash_map (内含一个 hashtable) (非标准)
  • hash_multiset (内含一个 hashtable) (非标准)
  • hash_multimap (内含一个 hashtable) (非标准)

熟悉关联式容器,需要有 RB-tree(红黑树原理) 和 hash table(哈希表原理) 基础。

算法(algorithm)

算法:各种常用算法如 sort,search,copy,erase 等,从实现的角度来看,STL 算法是一种 function template。

所有泛型算法的前两个参数都是一对迭代器,STL 习惯使用前闭后开的区间,[first, last)。

最后一个元素的下一位置,称为 end()。

数值的传递由 pass-by-value 改为 pass-by-reference,好处是,在模板中,参数的类型可以任意,当对象一大,传递成本便会上升,所以用 pass-by-reference 可以节省空间。

数值算法 <stl_numeric.h>

STL 将数值算法的内部实现放在 <stl_numeric.h> 中,用户调用数值算法的接口,需要包含 头文件。  

元素累加 accumulate

算法 accumulate 用来计算 init 和 [first, last) 内所有元素的总和。

相邻元素的差值 adjacent_difference

算法 adjacent_difference 用来计算 [first, last) 中相邻元素的差值。

内积 inner_product

算法 inner_product 计算 [first1, last1) 和 [first2, first2 + (last1 - first1)) 的一般内积。 

局部求和 partial_sum

算法 partial_sum 用来计算局部求和。

幂次方 power

算法 power 用来计算某数的 n 幂次方。

递增 iota

在某区间 [first, last) 填入某指定值 value 的递增序列。

基本算法 <stl_algobase.h>

判断两个区间是否相等 equal

如果两个序列在 [first, last) 区间内相等,equal() 返回 true。

注意,如果第二个序列的元素比较多,多出来的元素不予考虑,只要与第一个序列的元素相等,就返回 true。

改填元素 fill

将 [first, last) 内的所有元素改填新值。

改填元素的值 n 次 fill_n

将 [first, last) 内的前 n 个元素改填新值,返回的迭代器指向被填入的最后一个元素的下一位置。

元素互换 iter_swap

将两个 Forwarditerators 所指的对象对调。

以字典顺序进行比较 lexicographical_compare

以 "字典排列方式" 对两个序列 [first1, last1) 和 [first2, last2) 进行比较。

最大值 max

取两个对象的较大值。

最小值 min

取两个对象的最小值。

找出不匹配的点 mismatch

用来平行比较两个序列,指出两者之间的第一个不匹配点。

交换 swap

该函数用来交换两个对象的内容。

复制 copy

copy 算法可将输入区间 [first, last) 内的元素复制到输出区间 [result, result + (last-first)) 内。

逆向复制 copy_backward

将 [first, last) 区间内的每一个元素,以逆行的方向复制到以 result-1 为起点,方向亦为逆行的区间上。

set 相关算法

  • 并集 set_union

算法 set_union 可构造 S1、S2 之并集(S1 U S2),此集合内含 S1 或 S2 内的每一个元素。其中 S1、S2 及其并集都是以排序区间表示。

  • 交集 set_intersection

算法 set_intersection 可构造 S1、S2 之交集。

  • 差集 set_difference

算法 set_difference 可构造 S1、S2 之差集。此集合内含出现于 S1 但不出现于 S2 的每一个元素。

  • 对称差集 set_symmetric_difference

算法 set_symmetric_difference 可构造 S1、S2 之对称差集。此集合内容出现于 S1 但不出现于 S2 以及 出现于 S2 但不出现 S1 的每一个元素。

heap 算法

头文件 <stl_heap.h>

  • make_heap() 建堆
  • pop_heap() 从堆中取出一个元素
  • push_heap() 将一个元素推进堆内
  • sort_heap() 对堆排序

其它算法 <stl_algo.h>

定义于 SGI <stl_algo.h> 内的所有算法。

查找相邻而重复的元素 adjacent_find

对一个序列,查找相邻元素值相等的第一个元素。

计数 count

将 [first, last) 区间内的每一个元素拿来和指定值 value 比较,并返回与 value 相等的元素个数。

在特定条件下计数 count_if

将指定操作(一个仿函数) pred 实施于 [first, last) 区间内的每一个元素身上,并将造成 pred 计算结果为 true 的所有元素的个数返回。

循环查找 find

循环查找 [first, last) 内的所有元素,找出第一个匹配条件的,返回指向该元素的迭代器。

在特定条件下循环查找 find_if

根据指定的 pred 运算条件,循环查找 [first, last) 内的所有元素,找出第一个令 pred 运算结果为 true,返回指向该元素的迭代器。

查找某个子序列的最后一次出现点 find_end

在序列一 [first, last) 所涵盖的区间中,查找序列二 [first, last) 的最后一个出现点。

查找某些元素的第一次出现点 find_first_of

本算法以 [first2, last2) 区间内的某些元素作为查找目标,寻找在 [first1, last1) 区间内的第一次出现地点。

对区间内的每一个元素进行某操作 for_each

将仿函数 f 作用于 [first, last) 区间内的每一个元素上。

以特定操作之运算结果填充特定区间内的元素 generate

将仿函数 gen 的运算结果赋值给 [first, last) 区间内的所有元素上。

以特定操作之运算结果填充 n 个元素内容 generate_n

将仿函数 gen 的运算结果填写在从迭代器 first 开发的 n 个元素身上。

应用于有序区间 includes

S1 和 S2 必须是有序集合,其中的元素可以重复,判断 S1 是否包含于 S2。includes 算法可供用户选择采用 less 或 greater 进行两元素的大小比较。

最大值所在位置 max_element

这个算法返回一个迭代器,指向序列之中数值最大的元素。

应用于有序区间的合并操作 merge

将两个经过排序的集合 S1 和 S2,合并起来置于另一段空间。所得结果也是一个有序序列。

最小值所在位置 min_element

这个算法返回一个迭代器,指向序列之中数值最小的元素。

分割 partition

partition 将区间 [first, last) 中的元素重新排列。所有被一元条件运算 pred 判定为 true 的元素,都会被放在区间的前段,被判定为 false 的元素,都会被放在区间的后段。

如果需要保留原始相对位置,应使用 stable_partition。

移除并不删除 remove

移除 [first, last) 之中所有与 value 相等的元素,这一算法并不真正从容器中删除那些元素,而是将每一个不与 value 相等的元素轮番赋值给 first 之后的空间。

移除某类元素并将结果复制到另一容器 remove_copy

移除 [first, last) 之中所有与 value 相等的元素。它并不真正从容器中删除那些元素,而是将结果复制到一个以 result 标示起始位置的容器身上。

有条件地删除某类元素 remove_if

移除 [first, last) 区间内所有被仿函数 pred 认定为 true 的元素,每一个不符合 pred 条件的元素都会被轮番赋值给 first 之后的空间。

有条件地删除某类元素并将结果复制到另一容器 remove_copy_if

移除 [first, last) 区间内所有被仿函数 pred 认定为 true 的元素,它并不真正从容器中删除那些元素,而是将结果复制到一个以 result 标示起始位置的容器身上。

替换某类元素 replace

将 [first, last) 区间内的所有 old_value 都以 new_value 取代。

替换某类元素,并将结果复制到另一个容器 repalce_copy

唯一不同的是新序列会被复制到 result 所指的容器中。

有条件地替换 replace_if

将 [first, last) 区间内的所有被 pred 评估为 true 的元素,都以 new_value 取而代之。

有条件地替换,并将结果复制到另一个容器 replace_copy_if

行为与 replace_if() 类似,新序列会被复制到 result 所指的区间内。

反转元素次序 reverse

将序列 [first, last) 的元素在原容器中颠倒重排。

反转元素次序并将结果复制到另一个容器 reverse_copy

行为与 reverse() 类似,新序列会被复制到 result 所指的容器中。

旋转 rotate

将 [first, middle) 内的元素和 [middle, last) 内的元素互换,middle 所指的元素会成为容器的第一个元素。

旋转,并将结果复制到另一个容器 rotate_copy

行为与 rotate_copy() 类似,新序列会被复制到 result 所指的容器中。

查找某个子序列 search

在序列一 [first1, last1) 所涵盖的区间中,查找序列二 [first2, last2) 的首次出现点。

查找连续发生 n 次的子序列 search_n

在序列 [first, last) 所涵盖的区间中,查找连续 count 个符合条件之元素所形成的子序列,并返回一个迭代器指向该子序列起始处。

指定区间交换 swap_ranges

将 [first1, last1) 区间内的元素与从 first2 开始、个数相同的元素互相交换。这两个序列可位于同一容器中,也可位于不同的容器中。

以两个序列为基础,交互作用产生第三个序列 transform

transform() 两个版本都执行结果放进迭代器 result 所标示的容器中。

将重复的元素删除,只保留一个 unique

算法 unique 能够移除重复的元素。每当在 [first, last) 内遇到重复元素群,它便移除该元素群中第一个以后的所有元素。

将重复的元素删除,只保留一个, 并复制 result 中unique_copy

算法 unique_copy 可从 [first, last) 中将元素复制到以 result 开头的区间上。

lower_bound (应用于有序区间)

二分查找,在已排序的 [first, last) 中的寻找元素 value,返回位置。

upper_bound (应用于有序空间)

二分查找,在已排序的 [first, last) 中的寻找元素 value,与 lower_bound 区别是返回查找值的位置。

二分查找 binary_search (应用于有序空间)

二分查找法,在已排序的 [first, last) 中的寻找元素 value,查找到,返回 true,否则 false。

求下一个排列组合 next_permutation

next_permutation() 获取 [first, last) 所标示之序列的下一个排列组合。

实现原理:

在当前序列中,从尾端往前寻找两个相邻元素,前一个记为 *i,后一个记为 *ii,并且满足 *i < *ii。然后再从尾端寻找另一个元素 *j,如果满足 *i < *j,即将第 i 个元素与第 j 个元素对调,并将第 ii 个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。

求上一个排列组合 prev_permutation

prev_permutation() 获取 [first, last) 所标示之序列的上一个排列组合。

实现原理:

在当前序列中,从尾端往前寻找两个相邻元素,前一个记为 *i,后一个记为 *ii,并且满足 *i > *ii。然后再从尾端寻找另一个元素 *j,如果满足 *i > *j,即将第 i 个元素与第 j 个元素对调,并将第 ii 个元素之后(包括ii)的所有元素颠倒排序,即求出上一个序列了。

随机重排元素 random-shuffle

这个算法将 [first, last) 的元素次序随机重排, 在 N!种可能的元素排列顺序中随机选出一种,此处 N 为 last-first。

局部排序 partial_sort/partial_sort_copy

本算法接受一个 middle 迭代器(位于序列 [first, last) 之内),然后重新安排 [first, last),使序列中的 middle-first 个最小元素以递增顺序排序,置于 [first, middle)内。其余 last-middle 个元素安置于 [middle, last) 中,不保证有任何特定顺序。

排序算法 sort

STL 的 sort 算法,数据量大时采用 Quick Sort,分段递归排序,当数据量小于某个门槛(5-20),就改用 Insertion Sort。

Insertion Sort

插入排序是以双层循环的形式进行。时间复杂度为 O(N^2)。

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

Quick Sort

平均时间复杂度为 O(NlogN),最坏情况下将达 O(N^2)。

STL 早期采用 Quick Sort,现在 SGI STL 改用 IntroSort(极类似 median-of-three QuickSort 的一种排序算法)。

递归

Median-of-Three(三点中值) 中间值

Partitioning 分割

SGI STL sort

混合式排序算法,Introspective Sorting,当做 Partitioning 操作,有恶化为二次行为的倾向时,改用 Heap Sort,使其效率维持在 Heap Sort 的 O(NlogN)。

用 __lg() 控制分割恶化的情况:

// 找出 2^k <= n 的最大值 k

template <class size>

inline Size __lg(Size n) {

    Size k;

    for (k = 0; n > 1; n >>= 1)

        ++k;

        

    return k;

}

最多允许分割 2k 层;

混合式排序思想:

  • 先判断序列大小,当大于阈值 __stl_threshold(16),再检查分割层次,如果分割层次超过指定值 0,就改用 Heap Sort完成;
  • 在大于阈值 __stl_threshold(16),分割层次不为 0,就继续使用 Quick Sort;
  • 如果小于阈值,则用插入排序;

equal_range(应用于有序区间)

算法 equal_range 是二分查找法的一个版本,试图在已排序的 [first, last) 中寻找 value。

返回一个上下区间。

inplace_merge(应用于有序区间)

合并 并且 就地排序

nth_element

使第 n 大元素处于第 n 位置(从0开始,其位置是下标为 n的元素),并且比这个元素小的元素都排在这个元素之前,比这个元素大的元素都排在这个元素之后,但不能保证他们是有序的。

归并排序 merge sort

Merge Sort 的复杂度为 O(NlogN)。需要借用额外的内存。底层是调用 inplace_merge 实现。

仿函数/函数对象(functor/function object)

仿函数/函数对象:行为类似函数,可作为算法的某种策略,从实现的角度来看,仿函数是一种重载了 operator() 的 class 或 class template。STL 仿函数应该有能力被函数配接器(function adapter)修饰,为了拥有配接能力,每一个仿函数必须定义自己的相应类型。

以操作数的个数划分,可分为一元和二元仿函数

  • unary_function
unary_function 用来呈现一元函数的参数类型和返回值类型。

// 一元函数的参数类型和返回值类型

template <class _Arg, class _Result>

struct unary_function {

  typedef _Arg argument_type;

  typedef _Result result_type;

};

binary_function
binary_function 用来呈现二元函数的第一参数类型、第二参数类型,以及返回值类型。

// 二元函数的第一个参数类型和第二个参数类型,以及返回值类型

template <class _Arg1, class _Arg2, class _Result>

struct binary_function {

  typedef _Arg1 first_argument_type;

  typedef _Arg2 second_argument_type;

  typedef _Result result_type;

};

功能划分

算法类(Arithmetic)仿函数

STL 内建的 "算术类仿函数",支持加法、减法、乘法、除法、模数和求反运算符。,

关系运算类(Relational)仿函数

STL 内建的 "关系运算类仿函数" 支持等于、不等于、大于、大于等于、小于、小于等于六种运算。

逻辑运算类(Logical)仿函数

STL 内建的 "逻辑运算类仿函数" 支持了逻辑运算种的 And、Or、Not 三种运算。

identity、select、project

将其参数原封不动地传回。为了间接性——间接性是抽象化的重要工具。

适配器(adapter)

适配器:一种用来修饰容器、仿函数或迭代器接口的东西。例如,STL 提供的 queue 和 stack,虽然看似容器,其实只能算是一种容器适配器,因为它们的底部完全借助 deque,所有操作都由底层的 deque 供应。改变 functor 接口者,称为 function adapter等。

适配器(adapter) 在 STL 组件的灵活组合运用功能上,扮演者转换器的角色。

应用于容器,container adapter

STL 提供的两个容器 queue 和 stack,它们修饰 deque 的接口而形成的。

应用于迭代器,iterator adapter

STL 提供了许多应用于迭代器的适配器。

insert iterator

插入迭代器内部都维护有一个容器,容器当然有自己的迭代器,当客户端对插入迭代器做赋值操作时,就在插入迭代器中被转为对该容器的迭代器做插入操作。

back_insert_iterator

用于在容器尾部插入的迭代器适配器。

front_insert_iterator

用于在容器头部插入的迭代器适配器。

reverse iterator

将迭代器的移动行为倒转。以尾到头的方向来处理序列中的元素。

stream iterator

将迭代器绑定到一个 stream 对象身上,绑定到 istream 对象为 istream_iterator,拥有输入能力。

绑定到 ostream 对象为 ostream_iterator,拥有输出能力。

应用于仿函数,function adapter

对返回值进行逻辑否定:not1, not2

对参数进行绑定:bind1st, bind2nd

用于函数合成:compose1, compose2

用于函数指针:ptr_fun

用于成员函数指针:mem_fun, mem_fun_ref

来源于  GitHub  整理