本文为阅读《STL源码剖析》所作的读书笔记,仅供自己学习备份。
- STL设计的目的
建立数据结构和算法的一套标准,并且降低期间的耦合关系以提升各自的独立性、弹性、交互操作性。 - 组成
- 迭代器:设计适当的响应型别
- 容器:设计适当的迭代器
- 算法:完全独立于容器和迭代器之外自行发展,只要设计时以迭代器为对外接口就行
STL六大组件
容器(containers),指各种数据结构:vector、list、deque、set、map。从实现的角度,STL容器是一种class template。
算法(algorithms),常用算法例如sort、search、copy、erase…
迭代器(iterators),容器与算法之间的胶合剂,是所谓的“泛型指针”
仿函数(functors),行为类似函数,可作为函数的某种策略
配接器(adapters),一种修饰容器或仿函数或迭代器接口的东西,例如queue和stack,底层借助于deque
配置器(allocators),空间配置器环境组态
stl_config.h
中定义许多常量,标识某些组态的成立与否。所有的STL头文件都会直接或者间接包含这个组态文件,并以条件式写法,让预处理器根据各个常量决定取舍哪一段程序代码临时对象的产生和运用
临时变量,在型别名称之后加上一对小括号,可指定初值,其意义相当于调用相应的constructor并且不指定对象名称,例如vector(n,1)用于初始化一个二维vector前闭后开区间表示法
任何一个STL算法需要获得一对迭代器(泛型指针)所标示的区间,用于表示操作的范围。一般是前闭后开的区间,[begin, last),迭代器last指的是最后一个元素的下一个位置for(;first!=last;first++){ ...//do sth }
函数指针
将一整组操作当做参数来传递,可通过函数指针来达成
缺点在于无法持有自己的状态(所谓的局部状态),也无法达到组件技术中的可适配性(无法再将某些修饰条件加在它上面从而改变它的状态)仿函数
使用起来像函数一样的东西,例如针对某个class进行operator()重载template <class T> struct plus{ T operator()(const T&x, const T&y) const{ return x+y;} } //产生放函数对象 plus<int> plusobj; //使用仿函数 cout<<plusobj(3,5)<<endl;
plus非常接近STL的实现,唯一差别在于它缺乏“可配接能力”
SGI空间配置器
STL的操作对象(所有的数值)都存放在容器之内,而容器一定需要配置空间以置放资料。- 标准的空间配置器:std::allocator,因为效率低,一般不建议使用。只是对基层内存配置/释放行为::operator new和::operator delete做了薄薄的包装
- 特殊的空间配置器:std:alloc
c++内存配置操作和释放操作
new阶段:调用::operator new配置内存::construct();调用构造函数构造对象内容alloc::allocate()
delete阶段:调用析构函数::destroy();调用::operator delete释放内存alloc::deallocate()空间的配置和释放
SGI以malloc()和free()完成内存的配置和释放。考虑到小型区块可能造成内存破碎问题,SGI设计了双层级配置器。第一层直接用malloc和free,第二级配置器视情况采用不同策略。如果配置区别大于128bytes,选用第一级配置器;当小于128bytes时,采用复杂的memory pool的整理方式。是否同时开放第二级配置器,取决于__USE_MALLOC是否被定义。内存基本处理工具
- construct()接受一个指针和一个初值value,用途是将初值设定到指针所指的空间上
- destroy()有两个版本,一个版本是接受一个指针,准备将该指针所指之物析构掉;版本2是接受first和last两个迭代器,将[first, last)范围内所有对象析构掉(要考虑效率问题)
- uninitialized_copy使我们能够将内存的配置与对象的构造函数分离开来。对于输入范围的每个对象产生一个复制品,放入输出范围中。
- uninitialized_fill使我们能够将内存的配置与对象的构造函数分离开来。如果输入范围内的迭代器指向未初始化的内存,那么会在该范围内产生x的复制品。
- unintialized_fill_n使我们能够将内存的配置与对象的构造函数分离开来。会为指定范围内的所有元素设定相同的初值
POD(plain old data)
标量型别或传统的c struct型别,有trival/ctor/dtor/copy/assignment函数。
对于POD型别,一般是直接调用copy函数/最有效的初值填写手法fill()/
对于非POD型别,一般逐个使用构造函数/一个一个元素构造/迭代器模式
提供一种方法,使之能够依序巡访某个聚合物(容器)所含的各个元素,而又无需暴露该聚合物的内部表达方式迭代器
迭代器是一种类似指针的对象,最重要的工作就是内容提领(deference)和成员访问(member access),因此最重要的是对operator*和operator->进行重载工作。迭代器表达方式
- constant iterator:不允许改变“所指对象的内容”
- mutable iterator:允许改变“所知对象的内容”
Traits编程技法
凡原生指针,都没有能力定义自己的相应型别
凡class-type iterators,都有能力定义自己的相应型别
通过class template partial specialization的作用,不论是原生指针还是class-type iterator,都可以让外界很方便地获取其相应型别。
traits就是一个特性萃取机,获取各个迭代器的特性(相应型别)template <class I> struct iterator_traits{ typedef typename I::value_type value_type; }
如果traits能够有效运作,每个迭代器必须遵循约定,自行以内嵌型别定义的方式定义出相应型别。
traits技法大量使用于STL实现品中,利用“内嵌型别”的编程技巧与编译器的template参数推导功能,增强c++未能提供的关于型别认证方面的能力,弥补c++不为强型别语言的遗憾。迭代器的相应型别
- value type:指迭代器所指对象的型别
- difference type:表示两个迭代器之间的距离
- pointer:传回一个左值,令它代表p所指之物的地址
- reference:传回一个左值,令它代表p所指之物
- iterator catagory:产生一个临时变量,其型别隶属于四个迭代器类型(I、F、B、R)之一,然后根据这个型别,编译器再决定用哪个重载函数
template<class InputeIterator, class Distance> inline void advance(InputeIterator& i, Distance n);
iterator_traits
萃取迭代器的特性__type_traits
萃取型别(type)的特性,这个型别是否具备non-trivial defalt ctor/non-trival-copy ctor/non-trival assignment operator -