diff --git a/README.md b/README.md index b1714aa..0a6b4a6 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,31 @@ 学习资料:https://chenxiaowei.gitbook.io/cpp_concurrency_in_action/ +#### 2.4 [STL源码剖析](./stl_src) + +**stl源码剖析:gcc4.9.1** + +- [array](./stl_src/array.md) +- [deque](./stl_src/deque.md) +- [queue and stack](./stl_src/queue_stack.md) +- [list](./stl_src/list.md) +- [vector](./stl_src/vector.md) +- [typename](./stl_src/typename.md) +- [traits](./stl_src/traits.md) +- [iterator](./stl_src/iterator.md) +- [谈谈STL设计之EBO优化](./stl_src/谈谈STL设计之EBO优化.md) +- [rb_tree](./stl_src/rb_tree.md) +- [set and multiset](set_multiset.md) +- [map and multimap](./stl_src/map_multimap.md) +- [hashtable](./stl_src/hashtable.md) +- [myhashtable](./stl_src/myhashtable.md) +- [unordered_map](./stl_src/unordered_map.md) + + ### 3.代码运行 代码运行: - 全部在linux下用vim编写,使用gcc/g++调试!全部可正常运行! - + 全部在linux下用vim编写,使用gcc/g++调试!全部可正常运行! ## 关于作者: diff --git a/stl_src/array.md b/stl_src/array.md new file mode 100644 index 0000000..5e3c343 --- /dev/null +++ b/stl_src/array.md @@ -0,0 +1,146 @@ +# C++ STL源码剖析 tr1与std array + +## 0.导语 + +源码剖析版本为gcc4.9.1。 + +C++ tr1全称Technical Report 1,是针对C++标准库的第一次扩展。即将到来的下一个版本的C++标准c++0x会包括它,以及一些语言本身的扩充。tr1包括大家期待已久的smart pointer,正则表达式以及其他一些支持范型编程的内容。草案阶段,新增的类和模板的名字空间是std::tr1。 + +## 1.std::tr1::array + +使用: +``` +#include +std::tr1::array a; +``` + +tr1中的array比较简单,模拟语言本身的数组,并且让其支持迭代器操作,使其同其他容器一样,能够调用算法。对于tr1中array没有构造与析构。迭代器是直接使用传递进来的类型定义指针。 + +简单的看一下这个静态数组array源码: + +```cpp +template +struct array +{ + typedef _Tp value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef value_type* iterator; + typedef const value_type* const_iterator; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +} +``` +里面使用`reverse_iterator`作为rbegin与rend操作的迭代器。 +看上去上面一个迭代器,实际上两个,还有一个iterator,这个直接使用传递进来的类型定义指针,作为迭代器。 + +可以将其对比为vector中的正向与反向迭代器。 + +值得注意的是,在tr1::array中,支持传递数组大小为0,例如我们使用如下: + +``` +std::tr1::array a; +``` +对于这样的写法,会对应到下面: +``` +// Support for zero-sized arrays mandatory. +value_type _M_instance[_Nm ? _Nm : 1]; +``` +根据传递进来的大小,如果不为0,就是传递进来的大小,否则为1。 + + +## 2.std::array + +使用 +``` +std::array a; +``` + +std中的array包含了 + +![std_array.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/std_array.png) + +对比tr1与std的array + +```cpp +template +struct array +{ + typedef _Tp value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef value_type* iterator; + typedef const value_type* const_iterator; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + // Support for zero-sized arrays mandatory. + typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type; // # define _GLIBCXX_STD_C std + typename _AT_Type::_Type _M_elems; +} +``` + +发现array里面有两处值得注意的地方: +```cpp +// Support for zero-sized arrays mandatory. +typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type; // # define _GLIBCXX_STD_C std +typename _AT_Type::_Type _M_elems; +``` + +在源码中去找__array_traits,看到: +```cpp +template +struct __array_traits +{ + typedef _Tp _Type[_Nm]; + + static constexpr _Tp& + _S_ref(const _Type& __t, std::size_t __n) noexcept + { return const_cast<_Tp&>(__t[__n]); } +}; +``` + +上面两行的代码可以理解为下面: + +```cpp +typedef _Tp _Type[100]; +typedef _Type _M_elems; // 一个含有100个元素的数组。 +``` + +在实际写代码的时候,如果要定义一个数组,我们可以这样写: +```cpp +int a[100]; +//或者 +typedef int T[100]; +typedef T a; +``` +针对传进来的size处理,相比于tr1,更加复杂,使用了模板偏特化来处理传递size为0情况。 + +```cpp +template +struct __array_traits +{ + typedef _Tp _Type[_Nm]; + + static constexpr _Tp& + _S_ref(const _Type& __t, std::size_t __n) noexcept + { return const_cast<_Tp&>(__t[__n]); } +}; + +template +struct __array_traits<_Tp, 0> +{ + struct _Type { }; + + static constexpr _Tp& + _S_ref(const _Type&, std::size_t) noexcept + { return *static_cast<_Tp*>(nullptr); } +}; +``` + diff --git a/stl_src/deque.md b/stl_src/deque.md new file mode 100644 index 0000000..a89e0be --- /dev/null +++ b/stl_src/deque.md @@ -0,0 +1,506 @@ + +# C++ STL源码剖析之序列式容器deque +## 0.导语 +deque是一种双向开口的分段连续线性空间(简单理解为:双端队列),可以在头尾端进行元素的插入和删除。 + +deque与vector最大的差异就是: + +- deque允许于常数时间内对头端进行插入或删除元素; + +- deque是分段连续线性空间,随时可以增加一段新的空间; + +deque不像vector那样,vector当内存不够时,需重新分配/复制数据/释放原始空间;不过deque的迭代器设置比vector复杂,因为迭代器不能使用普通指针,因此尽量使用vector。 + +## 1.deque中控器 + +用户看起来deque使用的是连续空间,实际上是**分段连续线性空间**。为了管理分段空间deque容器引入了map,称之为中控器,map是一块连续的空间,其中每个元素是指向缓冲区的指针,缓冲区才是deque存储数据的主体。 + + +![deque_r.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/deque_r.png) + +在上图中,buffer称为缓冲区,显示map size的一段连续空间就是中控器。 + +中控器包含了map size,指向buffer的指针,deque的开始迭代器与结尾迭代器。 + +```cpp +_Tp **_M_map; +size_t _M_map_size; +iterator _M_start; +iterator _M_finish; +``` + +由于buffer也是指针,所以`_Tp`是指针的指针。 + +deque继承自`_Deque_base`,而`_Deque_base`里面有一个`_M_impl`。 + +![deque_base.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/deque_bacse.png) + +根据下图与上述描述,可以知道,中控器是由`_Deque_impl`实现的。 + +![impl.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/impl.png) + +而deque是使用基类`_Deque_base`来完成内存管理与中控器管理。 + + +## 2.高端的迭代器 + +对于deque来说,它的迭代器设计的非常棒! + +如下图所示: +![deque_iterator.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/deque_iterator.png) + +首先来看一下比较重要的成员: + +```cpp +typedef _Tp **_Map_pointer; +_Tp *_M_cur; +_Tp *_M_first; +_Tp *_M_last; +_Map_pointer _M_node; +``` + +这几个究竟是什么呢,根据名字,很容易知道啥意思,对于deque来说,是分段连续空间,迭代器执行操作,上述的`_M_cur`指向具体的元素,`_M_first`指向这段buffer中的第一个元素,`_M_last`指向最后一个元素(不是有效的元素),而`_M_node`则是指向中控器。所以它是一个指针的指针。 + + +例如现在迭代器执行++操作,当前buffer不够用了,那么此时需要一个指针能够回到中控器,取下一段buffer,重置`_M_first`与`_M_last`的指针位置,`_M_cur`指向新段buffer中的指定位置。 + +我们现在回到一开始的图: + +![deque_r.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/deque_r.png) + +最上面的的iterator就是上面几个指针的区块配图。 + + +那buffer计算是什么实现的呢? + +在源码中计算是根据传递进来的类型,如果传递的类型小于512字节,那么buffersize就是512/sizeof(_Tp),超过512,就是1。 + + +```cpp +static size_t _S_buffer_size() +_GLIBCXX_NOEXCEPT +{ + return(__deque_buf_size( sizeof(_Tp) ) ); +} + +``` +`__deque_buf_size`实现 + +```cpp +#ifndef _GLIBCXX_DEQUE_BUF_SIZE +#define _GLIBCXX_DEQUE_BUF_SIZE 512 +#endif +inline size_t +__deque_buf_size( size_t + __size ) +{ + return(__size < _GLIBCXX_DEQUE_BUF_SIZE + ? size_t( _GLIBCXX_DEQUE_BUF_SIZE / __size ) : size_t( 1 ) ); +} +``` +前面几节源码中提到了萃取机技术,针对每个迭代器都需要嵌入下面五种typedef: + +```cpp +typedef std::random_access_iterator_tag iterator_category; +typedef _Tp value_type; +typedef _Ptr pointer; +typedef _Ref reference; +typedef ptrdiff_t difference_type; +``` + +据此,也可以知道deque迭代器的使用的是随机访问迭代器:`random_access_iterator_tag`。 + +而vector使用的迭代器也是这个,根据侯捷老师所讲,连续的buffer是vector,这与迭代器的tag类型不谋而合。 + + +下面来看一下这个强大的迭代器的一些操作符重载: + +具体的讲解在代码里面说。 + +> 取值操作符 + +```cpp +reference +operator*() const +_GLIBCXX_NOEXCEPT +{ + return(*_M_cur); +} + + +pointer +operator->() const +_GLIBCXX_NOEXCEPT +{ + return(_M_cur); +} +``` +当然上述的`->`也可以直接调用`*`操作符来实现,例如: + +```cpp +pointer +operator->() const +_GLIBCXX_NOEXCEPT +{ + return &(operator*()); +} +``` + +> ++与--操作符 + +```cpp + +// 前置++操作符 +_Self & +operator++() +_GLIBCXX_NOEXCEPT +{ + // 先++,判断是否到了buffer的末尾,如果到了末尾,就要跳到下一个buffer。 + ++_M_cur; + if ( _M_cur == _M_last ) // _M_last指向的不是有效元素,保留节点 + { + _M_set_node( _M_node + 1 ); + _M_cur = _M_first; + } + return(*this); +} + +// 后置++操作符 +_Self +operator++( int ) +_GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; + ++*this; + return(__tmp); +} + +// 前置--操作符 +_Self & +operator--() +_GLIBCXX_NOEXCEPT +{ + // 先判断是否到了起始位置,如果到了,由于需要进行--操作,那么就应该进入前一个buffer + if ( _M_cur == _M_first ) + { + _M_set_node( _M_node - 1 ); + _M_cur = _M_last; + } + --_M_cur; + return(*this); +} //先在容器头部插入与第一个元素相同的元素 + +// 后置--操作符 +_Self +operator--( int ) +_GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; /* 定义一个副本 */ + --*this; /* 迭代器自减操作 */ + return(__tmp); +} +``` + +> 跳跃n个距离操作符 + + +```cpp +/* +* 实现随机取,迭代器可以直接跳跃n个距离 +* 将迭代器前移n个距离,当n负值时就为下面的operator-=操作 +*/ +_Self & +operator+=( difference_type __n ) + +_GLIBCXX_NOEXCEPT +{ + const difference_type __offset = __n + (_M_cur - _M_first); + /* + * 若前移n个距离后,目标依然在同一个缓冲区 + * 则直接前移n个距离 + */ + if ( __offset >= 0 && __offset < difference_type( _S_buffer_size() ) ) + _M_cur += __n; + else { + /* + * 若前移n个距离后,目标超出了缓冲区范围 + * __offset>0 __offset / difference_type(_S_buffer_size())计算向后移动多少个缓冲区 + * __offset<=0 -difference_type((-__offset - 1) / _S_buffer_size()) - 1计算向前移动多少个缓冲区 + */ + const difference_type __node_offset = + __offset > 0 ? __offset / difference_type( _S_buffer_size() ) + : -difference_type( (-__offset - 1) + / _S_buffer_size() ) - 1; + /* 调整到正确的缓冲区,此时_M_first已经修改了 */ + _M_set_node( _M_node + __node_offset ); + /* 修改为正确的指针位置 */ + _M_cur = _M_first + (__offset - __node_offset + * difference_type( _S_buffer_size() ) ); + } + return(*this); +} + +``` +下面这几个操作符都是调用上面的`+=`操作符实现: + + +```cpp + +/* + * 操作符+重载 + * 返回操作之后的副本 + */ +_Self +operator+( difference_type __n ) const +_GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; + /* 调用operator+=操作 */ + return(__tmp += __n); +} + + +/* 利用operator+=操作实现 */ +_Self & +operator-=( difference_type __n ) +_GLIBCXX_NOEXCEPT +{ + return(*this += -__n); +} + + +/* + * 操作符-重载 + * 返回操作之后的副本 + */ +_Self +operator-( difference_type __n ) const +_GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; /* 保存副本 */ + return(__tmp -= __n); /* 调用operator-=操作符 */ +} + + +/* 返回指定位置的元素,即实现随机存取 */ +reference +operator[]( difference_type __n ) const +_GLIBCXX_NOEXCEPT +{ + return(*(*this + __n) ); /* 该函数调用operator+,operator* */ +} + +``` + + +> buffer跳跃 + +前面的++与--等操作符,会调用到`_M_set_node`函数,该函数的作用是能够进行buffer之间的跳跃,修改`_M_node`、`_M_first`、`_M_last`的指向。 + +```cpp +/** + * Prepares to traverse new_node. Sets everything except + * _M_cur, which should therefore be set by the caller + * immediately afterwards, based on _M_first and _M_last. + */ +void +_M_set_node( _Map_pointer __new_node ) +_GLIBCXX_NOEXCEPT +{ + _M_node = __new_node; /* 指向新的节点 */ + _M_first = *__new_node; /* 指向新节点的头部 */ + _M_last = _M_first + difference_type( _S_buffer_size() ); /* 指向新节点的尾部 */ +} +``` +据此,我们就把deque的迭代器实现细节讲解完毕了。 + + + +## 3.deque + +> begin()函数 + + +返回`_M_start`。 + +```cpp +iterator +begin() +_GLIBCXX_NOEXCEPT +{ + return(this->_M_impl._M_start); +} +``` + +> end()函数 + + +返回`_M_finish`。 + +```cpp +iterator +end() +_GLIBCXX_NOEXCEPT +{ + return(this->_M_impl._M_finish); +} + +``` + +> size()函数 + +```cpp +size_type +size() const + +_GLIBCXX_NOEXCEPT +{ + return(this->_M_impl._M_finish - this->_M_impl._M_start); +} +``` + +> resize()函数 + +根据传递进来的大小,如果超过了总size,就重新分配扩充`__new_size-size()`空间,否则删除从`size()-__new_size`数据,例如现在有20个空间,resize(12),就会把后面8个空间数据删除及空间释放。 + +```cpp +void +resize( size_type __new_size ) +{ + const size_type __len = size(); + if ( __new_size > __len ) + _M_default_append( __new_size - __len ); + else if ( __new_size < __len ) + _M_erase_at_end( this->_M_impl._M_start + + difference_type( __new_size ) ); +} +``` +> empty()函数 + +判断两个指针位置即可。 + +```cpp +bool +empty() const + +_GLIBCXX_NOEXCEPT +{ + return(this->_M_impl._M_finish == this->_M_impl._M_start); +} +``` +> back函数 + + +```cpp +reference +back() +_GLIBCXX_NOEXCEPT // 指向finish的前一个位置 +{ + iterator __tmp = end(); + --__tmp; + return(*__tmp); +} +``` + +> push_front函数 + + +```cpp +void +push_front( const value_type &__x ) +{ + //若当前缓冲区存在可用空间 + if ( this->_M_impl._M_start._M_cur != this->_M_impl._M_start._M_first ) + { + this->_M_impl.construct( this->_M_impl._M_start._M_cur - 1, __x );// 直接构造对象 + --this->_M_impl._M_start._M_cur; // 调整指针所指位置 + } else + _M_push_front_aux( __x ); // 需分配一段新的连续空间 +} +``` + +> push_back函数 + + +```cpp +void +push_back( const value_type &__x ) +{ + //若当前缓冲区存在可用空间 + if ( this->_M_impl._M_finish._M_cur + != this->_M_impl._M_finish._M_last - 1 ) + { + this->_M_impl.construct( this->_M_impl._M_finish._M_cur, __x ); // 直接构造对象 + ++this->_M_impl._M_finish._M_cur; //调整指针所指位置 + } else // 若当前缓冲区不存在可用空间 + // 需分配一段新的连续空间 + _M_push_back_aux( __x ); +} +``` +上述对应的pop动作与之相反。 + +> insert()函数 + +insert函数比较有意思,根据传递进来的迭代器位置,看是不在开头与结尾,如果是在开头直接调用`push_front`函数,结尾直接调`push_back`函数,否则在容器中直接插入元素。 + +```cpp +template +typename deque<_Tp, _Alloc>::iterator +deque<_Tp, _Alloc>:: +insert(iterator __position, const value_type& __x) +{ + if (__position._M_cur == this->_M_impl._M_start._M_cur) + { + push_front(__x); + return this->_M_impl._M_start; + } + else if (__position._M_cur == this->_M_impl._M_finish._M_cur) + { + push_back(__x); + iterator __tmp = this->_M_impl._M_finish; + --__tmp; + return __tmp; + } + else //否则在容器直接插入数据 + return _M_insert_aux(__position._M_const_cast(), __x); +} + +``` +而上述在容器中直接插入元素函数,会计算插入点,如果比较靠前面,就在前面插入,靠近后面就在后面插入: + + +```cpp + +template +typename deque<_Tp, _Alloc>::iterator +deque<_Tp, _Alloc>:: +_M_insert_aux(iterator __pos, const value_type& __x) +{ + value_type __x_copy = __x; // XXX copy + difference_type __index = __pos - this->_M_impl._M_start; //计算插入点之前元素个数 + if (static_cast(__index) < size() / 2) //若插入点之前的元素较少 + { + push_front(_GLIBCXX_MOVE(front())); //先在容器头部插入与第一个元素相同的元素 + iterator __front1 = this->_M_impl._M_start; + ++__front1; + iterator __front2 = __front1; + ++__front2; + __pos = this->_M_impl._M_start + __index; + iterator __pos1 = __pos; + ++__pos1; + _GLIBCXX_MOVE3(__front2, __pos1, __front1); // 元素搬移 + } + else + { + push_back(_GLIBCXX_MOVE(back())); + iterator __back1 = this->_M_impl._M_finish; + --__back1; + iterator __back2 = __back1; + --__back2; + __pos = this->_M_impl._M_start + __index; + _GLIBCXX_MOVE_BACKWARD3(__pos, __back2, __back1); + } + *__pos = _GLIBCXX_MOVE(__x_copy); // 在安插点上设定新值 + return __pos; +} +``` diff --git a/stl_src/hashtable.md b/stl_src/hashtable.md new file mode 100644 index 0000000..7db3180 --- /dev/null +++ b/stl_src/hashtable.md @@ -0,0 +1,567 @@ +# C++ STL源码剖析之哈希表 + +## 0.导语 + +哈希表,是作为`unordered_map`与`undered_set`等的底层容器,自gcc2.9后源码量大增! + +这次阅读的代码仍旧是gcc4.9.1,代码量非常多,就不全部展开,重点研究底层哈希的艺术与技术,似乎这两个词语很押韵哦,哈哈,进入正文~ + + +## 1.Hashtable初识 + +先来看一眼Hashtable源码: + +```cpp +template +class _Hashtable +:  public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal, + public __detail::_Map_base<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>, + public __detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>, + public __detail::_Rehash_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,_H1, _H2, _Hash, _RehashPolicy, _Traits>, + public __detail::_Equality<_Key, _Value, _Alloc, _ExtractKey, _Equal,_H1, _H2, _Hash, _RehashPolicy, _Traits>, + private __detail::_Hashtable_alloc >::__type> +{ +}; +``` + +没学过类模板的一脸懵逼,数一下模板参数都晕死。。。 + +还有它的继承,一下子整出这么多父亲来。。。 + +下面就来一一分析它的父亲,然后再回到哈希表。 + + +## 2._Hashtable_base + +其中注释中如下: + +> Helper class adding management of _Equal functor to _Hash_code_base type. + + +帮助程序类,将仿函数_Equal的管理添加到_Hash_code_base中。 + +对比代码就可以看出来是啥意思了: +```cpp +template +struct _Hashtable_base +: public _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash, _Traits::__hash_cached::value>, +private _Hashtable_ebo_helper<0, _Equal> +{ + +}; +``` +对比一下`_Hash_code_base`与`_Hashtable_base`,两者就差一个`_Equal`,据此这句话解释完毕。 + +它的基类又有两个分别是: + +```cpp +__detail::_Hash_code_base +__detail::_Hashtable_ebo_helper +``` + +我们继续追踪这两个类! + +### 2.1 _Hash_code_base + +这个类最后一个`__cache_hash_code`表示是否缓存hash code。 +```cpp +template +struct _Hash_code_base; +``` + +根据是否缓存,得到其偏特化版本: + +- 使用范围哈希(实际上就是我们通常说的除留余数法),不缓存hash code。 + +```cpp +template +struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash, false> +: private _Hashtable_ebo_helper<0, _ExtractKey>, +private _Hashtable_ebo_helper<1, _Hash> +} +``` +- 使用范围哈希(实际上就是我们通常说的除留余数法),缓存hash code。 + +对于这个偏特化,缓存是没有必要的,所以代码中只是声明,并没有定义! + +```cpp +template +struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash, true>; +``` +- 有哈希函数以及范围哈希函数,不缓存hash code。 + +```cpp +template +struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, +_Default_ranged_hash, false> +: private _Hashtable_ebo_helper<0, _ExtractKey>, +private _Hashtable_ebo_helper<1, _H1>, +private _Hashtable_ebo_helper<2, _H2> +{ +}; +``` +- 上述的缓存hash code + +```cpp +template +struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, + _Default_ranged_hash, true> +: private _Hashtable_ebo_helper<0, _ExtractKey>, + private _Hashtable_ebo_helper<1, _H1>, + private _Hashtable_ebo_helper<2, _H2> +{ +``` + +上述_H1与_H2大家肯定很迷惑,下面来看一下: + +(1) Default range hashing function(默认范围哈希函数) + +```cpp +h1=hash +``` +下面这个就是: + +```cpp +h2(h1(key),N)=h1(key)%N +``` +具体可以在后面看到阐述。 +```cpp +struct _Mod_range_hashing +{ + typedef std::size_t first_argument_type; + typedef std::size_t second_argument_type; + typedef std::size_t result_type; + + result_type + operator()(first_argument_type __num, + second_argument_type __den) const noexcept + { return __num % __den; } +}; +``` +别看使用一个struct定义的,大家会以为是类,实际上重载了()操作符,就是个仿函数。 + +上面对应到哈希表数据结构中,就是大家知道的散列函数:**除留余数法**。 + +``` +f(__num) = __num mod __den(__den<=__num) +``` +其次,是`_Default_ranged_hash`: + +```cpp +struct _Default_ranged_hash { }; +``` +这个只是作为标记用,默认已经计算的范围哈希函数( Default ranged hash function): +``` +h(k, N) = h2(h1(k), N), +``` +所以到这,底层的哈希表的散列函数很明显了,默认就是这样的。 +而刚才提到的标记就是由于类型H1与H2的对象组合成H,会消耗额外的拷贝操作,因此这里引出了这个标记。 + +至此,上面提到的_H1与_H2讲解完毕,就是分别对应上述两个函数。 + + +(2) rehash操作 + +紧接着,还有个比较重要的称为rehash,相信大家很清楚rehash,当散列表的冲突到达一定程度,那么就需要重新将key放到合适位置,而哈希表的底层源码就是这样做的,这里封装成了一个rehash policy: +```cpp +struct _Prime_rehash_policy +{ + //... +}; +``` +rehash操作中提到:桶的大小(bucket size) 默认通常是最小的素数,从而保证装载因子(load factor 容器当前元素数量与桶数量之比。)足够小。装载因子用来衡量哈希表满的程度,最大加载因子默认值为1.0. +```cpp +_Prime_rehash_policy(float __z = 1.0) +: _M_max_load_factor(__z), _M_next_resize(0) { } + + +> rehash计算下一个素数桶 + +``` +当哈希冲突的时候,怎么rehash呢? +```c++ + inline std::size_t +_Prime_rehash_policy:: +_M_next_bkt(std::size_t __n) const +{ + const unsigned long* __p = std::lower_bound(__prime_list, __prime_list + _S_n_primes, __n); + _M_next_resize = + static_cast(__builtin_ceil(*__p * _M_max_load_factor)); +return *__p; +} +``` +当发生哈希冲突的时候,该函数会返回一个不小于n的素数来作为一下个桶。 + +> 素数表 + +怎么查找素数呢? +发现上面有个`__prime_list`,于是取查找,在`libstdc++v3/src/shared/hashtable-aux.cc`中找到了所有的素数表。 +里面总共有256+1+49或者256+49个。 +如果sizeof(unsigned long)!=8 就是256+1+49个,否则就是256+49个。 + + +```cpp +extern const unsigned long __prime_list[] = // 256 + 1 or 256 + 48 + 1 + { + 2ul, 3ul, 5ul, 7ul, 11ul, 13ul, 17ul, 19ul, 23ul, 29ul, 31ul, + 37ul, 41ul, 43ul, 47ul, 53ul, 59ul, 61ul, 67ul, 71ul, 73ul, 79ul, + 83ul, 89ul, 97ul, 103ul, 109ul, 113ul, 127ul, 137ul, 139ul, 149ul, + // 后面还有很多 + } +``` +所以一切都变得非常清晰,那就是通过lower_bound在上述表中去找第一个大于等于给定值n的素数。 + +``` +enum { _S_n_primes = sizeof(unsigned long) != 8 ? 256 : 256 + 48 }; +``` +> 计算元素对应的桶 + +根据最大加载因子算出最小的桶,然后根据桶计算出对于每个元素对应的最小素数桶。 + +```cpp +inline std::size_t +_Prime_rehash_policy:: +_M_bkt_for_elements(std::size_t __n) const +{ + // 获取最小的桶 + const float __min_bkts = __n / _M_max_load_factor; + // 获取最小素数p + const unsigned long* __p = std::lower_bound(__prime_list, __prime_list + + _S_n_primes, __min_bkts); + _M_next_resize = + static_cast(__builtin_ceil(*__p * _M_max_load_factor)); + return *__p; +} +``` + + +_Hashtable_ebo_helper就是前面学习过的EBO空基类 + + +`_Map_base`主要是通过偏特化,实现重载操作符`[]`与`at`。 + +`_Insert`主要完成插入相关。 + +`_Rehash_base`主要完成上述rehash中的最大加载因子值的传递。 + +`_Equality_base`主要是为类`_Equality`提供公共类型与函数。 + +到现在为止,上述的`_Hashtable`继承的所有类都阐述完毕。 + +## 2.hashtable中链表的节点结构 + +hash node基类,这个只包含指针声明。 + +```cpp +struct _Hash_node_base +{ + _Hash_node_base* _M_nxt; + + _Hash_node_base() noexcept : _M_nxt() { } + + _Hash_node_base(_Hash_node_base* __next) noexcept : _M_nxt(__next) { } +}; +``` +带节点值的类继承上述基类 +```cpp +template +struct _Hash_node_value_base : _Hash_node_base +{ + typedef _Value value_type; + + __gnu_cxx::__aligned_buffer<_Value> _M_storage; + + _Value* + _M_valptr() noexcept + { return _M_storage._M_ptr(); } + + const _Value* + _M_valptr() const noexcept + { return _M_storage._M_ptr(); } + + _Value& + _M_v() noexcept + { return *_M_valptr(); } + + const _Value& + _M_v() const noexcept + { return *_M_valptr(); } +}; + +``` +前面提到节点是否还有hash code,故在节点中应该得带hash code,而具体在下面中实现: + +```cpp +/** +* Primary template struct _Hash_node. +*/ +template +struct _Hash_node; + +/** +* Specialization for nodes with caches, struct _Hash_node. +* +* Base class is __detail::_Hash_node_value_base. +*/ +template +struct _Hash_node<_Value, true> : _Hash_node_value_base<_Value> +{ + std::size_t _M_hash_code; + + _Hash_node* + _M_next() const noexcept + { return static_cast<_Hash_node*>(this->_M_nxt); } +}; + +/** +* Specialization for nodes without caches, struct _Hash_node. +* +* Base class is __detail::_Hash_node_value_base. +*/ +template +struct _Hash_node<_Value, false> : _Hash_node_value_base<_Value> +{ + _Hash_node* + _M_next() const noexcept + { return static_cast<_Hash_node*>(this->_M_nxt); } +}; + +``` + +到这里就很明确了,对于节点,分为包含hash code与不包含,具体是根据传递的模板参数,来调用相应的偏特化版本。 + + +## 3.迭代器 + +迭代器基类显示使用using的语法,这个语法类似于typedef,后面定义就可以直接使用`__node_type`语法来定义,`_M_incr`函数完成链表下一个节点获取。 + +```cpp +/// Base class for node iterators. +template +struct _Node_iterator_base +{ + using __node_type = _Hash_node<_Value, _Cache_hash_code>; + + __node_type* _M_cur; + + _Node_iterator_base(__node_type* __p) noexcept + : _M_cur(__p) { } + + void + _M_incr() noexcept + { _M_cur = _M_cur->_M_next(); } +}; +``` + +节点迭代器:对下面代码研读,学习到两点: +- 第一:using 的使用 +- hashtable的迭代器属于forward_iterator +- 重载了++,--,*,->,这四个操作符 + +```cpp +template +struct _Node_iterator +: public _Node_iterator_base<_Value, __cache> +{ +private: + using __base_type = _Node_iterator_base<_Value, __cache>; + using __node_type = typename __base_type::__node_type; + +public: + typedef _Value value_type; + typedef std::ptrdiff_t difference_type; + typedef std::forward_iterator_tag iterator_category; + + using pointer = typename std::conditional<__constant_iterators, + const _Value*, _Value*>::type; + + using reference = typename std::conditional<__constant_iterators, + const _Value&, _Value&>::type; + + _Node_iterator() noexcept + : __base_type(0) { } + + explicit + _Node_iterator(__node_type* __p) noexcept + : __base_type(__p) { } + + reference + operator*() const noexcept + { return this->_M_cur->_M_v(); } + + pointer + operator->() const noexcept + { return this->_M_cur->_M_valptr(); } + + _Node_iterator& + operator++() noexcept + { + this->_M_incr(); + return *this; + } + + _Node_iterator + operator++(int) noexcept + { + _Node_iterator __tmp(*this); + this->_M_incr(); + return __tmp; + } +}; +``` + +## 4.仔细研究hashtable的重要内部结构 + +内部结构为在每个元素中维护一个单链表, 然后在单链表上执行元素的插入、搜寻、删除等操作,每个元素被称为桶(bucket),底层构建先采用H1计算出key的hash code,再通过除留余数法H2得到其对应的桶。 + +```cpp +template +class _Hashtable +private: + __bucket_type* _M_buckets; //_ Hash_node_base * + size_type _M_bucket_count; // bucket 节点个数 + __node_base _M_before_begin; // _NodeAlloc::value_type + size_type _M_element_count; // //hashtable中list节点个数 + _RehashPolicy _M_rehash_policy; // rehash策略 + __bucket_type _M_single_bucket; // 只需要一个桶用 +}; +``` +hashtable的一些重要函数: + +> begin函数 + +```cpp +iterator +begin() noexcept +{ return iterator(_M_begin()); } +``` + +调用`_M_begin`: + +可以把`_M_before_begin`想象成一个head节点,第一个节点就是下一个节点。 + +```cpp +__node_type* +_M_begin() const +{ return static_cast<__node_type*>(_M_before_begin._M_nxt); } +``` +> end函数 + +因为是单链表,返回最后一个即可。 + +```cpp +iterator +end() noexcept +{ return iterator(nullptr); } +``` + +> size与empty函数 + +```cpp +size_type +size() const noexcept +{ return _M_element_count; } + +bool +empty() const noexcept +{ return size() == 0; } +``` + +> 桶数量 + +```cpp +size_type +bucket_count() const noexcept +{ return _M_bucket_count; } +``` + +> 计算加载因子 + +当前元素数量除以桶的数量 + +```cpp +float +load_factor() const noexcept +{ + return static_cast(size()) / static_cast(bucket_count()); +} +``` +> 桶的index计算 + +根据传递进来的key获得桶的index。 + +```cpp +size_type +bucket(const key_type& __k) const +{ return _M_bucket_index(__k, this->_M_hash_code(__k)); } +``` +在`_Hash_code_base`中有如下实现: + +而`_M_h1`返回的是`_H1`,`_H1`不知道是什么情况下,我们可以在`unordered_map`中查找到是`_Hash=hash<_Key>`,因此下面这个函数就是数学表达式: +`h1(k)`来获取hash code。 + +返回桶的hash code。 + +```cpp +__hash_code +_M_hash_code(const _Key& __k) const +{ return _M_h1()(__k); } +``` + +返回桶的index。 + +`_M_bucket_index`在同一文件后面找到定义: + +```cpp +size_type +_M_bucket_index(const key_type& __k, __hash_code __c) const +{ return __hash_code_base::_M_bucket_index(__k, __c, _M_bucket_count); } +``` +我们继续去`__hash_code_base`查找`_M_bucket_index`,可在`bits/hashtable_policy.h`中找到: +```cpp +std::size_t +_M_bucket_index(const _Key&, __hash_code __c, + std::size_t __n) const +{ return _M_h2()(__c, __n); } +``` +同上述h1的查找,可以在`unordered_map`中查到`_H2`默认采用`_Mod_range_hashing`,再看这个源码: +```cpp +struct _Mod_range_hashing +{ + typedef std::size_t first_argument_type; + typedef std::size_t second_argument_type; + typedef std::size_t result_type; + + result_type + operator()(first_argument_type __num, + second_argument_type __den) const noexcept + { return __num % __den; } +}; +``` +对应数学表达式就是`h2(c,n)`。 + + +因此上述`bucket`获取桶的index对应的数学表达式就是: + +```cpp +h(k,hash(k))=h(k,hash(k),n)=h(k,hash(k)%n) +``` +实际上就是最终的: + +```cpp +hash(k)%n +``` +这个就是桶的index计算。 diff --git a/stl_src/iterator.md b/stl_src/iterator.md new file mode 100644 index 0000000..783dbbc --- /dev/null +++ b/stl_src/iterator.md @@ -0,0 +1,335 @@ +# C++ STL源码剖析之实现一个简单的iterator_category + +## 0.导语 + +本节使用上节Traits特性,研究iterator源码,来实现一个简单的iterator_category,同时对iterator的源码结构进行分析。 + +**知其然,知其所以然,源码面前了无秘密!** + +## 1.利用萃取机实现一个简单的iterator_category识别 + +上一节指出了迭代器的作用,依旧如下图所示: + +![](http://pxz2lirgn.bkt.clouddn.com/rela.png) + +迭代器是指向序列元素的指针的一种抽象。通过使用迭代器,我们可以访问序列中的某个元素、改变序列中的某个元素的值、使迭代器向前或向后行走等等。 + +迭代器有常见有五种类型: value_type, difference_type, reference_type, pointer_type都比较容易在 traits 和相应偏特化中提取。 + +但是,iterator_category一般也有5个,这个相应型别会引发较大规模的写代码工程。 + +- 单向移动只读迭代器 Input Iterator +- 单向移动只写迭代器 Output Iterator +- 单向移动读写迭代器 Forward Iterator +- 双向移动读写迭代器 Bidirectional Iterator + +![](http://pxz2lirgn.bkt.clouddn.com/iterator.png) + +例如:我们实现了 advanceII, advanceBI, advanceRAI 分别代表迭代器类型是Input Iterator,Bidirectional Iterator和Random Access Iterator的对应实现。 + +```c++ +template +void advance(Iterator& i) { + if (is_random_access_iterator(i)) + advanceRAI(i,n); + if (is_bidirectional_iterator(i)) + advanceBI(i,n); + else + advanceII(i,n); +} +``` + +但这样在执行时期才决定使用哪一个版本,会**影响程序效率**。最好能够在编译期就选择正确的版本。 + +而**重载**这个函数机制可以达成这个目标。 + +而对于`advanceXX()`都有两个函数参数,型别都未定(因为都是模板参数)。为了令其同名,形成重载函数,我们必须加上一个型别已确定的函数参数,使函数重载机制得以有效运作起来。 + +设计如下:如果**traits**有能力萃取出迭代器的种类,我们便可利用这个"迭代器类型"相应型别作为advancexx的第三个参数,而这个相应型别必须是一个class type,不能只是数值号码类的东西,因为编译器需依赖它来进行**重载决议**。 + +下面来进行实现,首先给出一个总体结构图: + +![](http://pxz2lirgn.bkt.clouddn.com/itera.png) + +定义出下面tag: + +```c++ +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 {}; +// 继承的好处就是,当函数需要用 input_iterator_tag 的时候 +// 假设你传进一个forward_iterator_tag,它会沿继承向上找,知道符合条件 +``` + +声明了一些列 tag 之后,我们就可以重载 advance函数,我们把这些函数用下滑线来定义,表示在内部使用,外部不可见。 + +```c++ +// 继承的好处就是,当函数需要用 input_iterator_tag 的时候 +// 假设你传进一个forward_iterator_tag,它会沿继承向上找,知道符合条件 +// input iterator +template +inline void __advance(inputIterator&i, distance n, + input_iterator_tag) { + std::cout << "input tag" << std::endl; +} +// output iterator +template +inline void __advance(outputIterator&i, distance n, + output_iterator_tag) { + std::cout << "output tag" << std::endl; +} + +// forward iterator +template +inline void __advance(ForwardIterator &i, Distance n, + forward_iterator_tag) { + std::cout << "forward tag" << std::endl; +} + +// bidrectional iterator +template +inline void __advance(BidiectionalIterator &i, Distance n, + bidiectional_iterator_tag) { + std::cout << "bidrectional tag" << std::endl; + +} + +// RandomAccess iterator +template +inline void __advance(RandomAccessIterator &i, Distance n, + random_access_iterator_tag) { + std::cout << "randomaccess tag" << std::endl; + +} +``` + +定义萃取机: + +```c++ +// traits 型别 +template +struct Iterator_traits { + typedef typename I::iterator_category iterator_category; +}; + +// 针对原生指针设计的"偏特化版" +template +struct Iterator_traits { + typedef random_access_iterator_tag iterator_category; +}; +template +struct Iterator_traits { + typedef random_access_iterator_tag iterator_category; +}; +``` + +对外暴露接口: + +```c++ +// 对外接口 +template +inline void advance(InputIterator &i, Distance n) { + // 通过Ierator_traits询问它的iterator_category是谁 + typedef typename Iterator_traits::iterator_category category; + __advance(i, n, category()); // 各型别的重载 +} + +``` + +定义class type: + +```c++ +// class type +template +struct iterator { + typedef Category iterator_category; +}; +``` + +开始测试,我们使用上述定义的class type与原生指针来测试,分别进入萃取机的普通萃取机与偏特化萃取机,看看是否得到相应的Tag。 + +```c++ +int main() { + iterator input; + iterator output; + iterator forward; + iterator bidect; + iterator random; + advance(input, 10); + advance(output, 10); + advance(forward, 10); + advance(bidect, 10); + advance(random, 10); + int *p=NULL; + advance(p,10); + return 0; +} +``` + +输出结果: + +```c++ +input tag +output tag +forward tag +bidrectional tag +randomaccess tag +randomaccess tag +``` + +一切如我们预期一样,通过萃取机,我们获得了每个迭代器的tag,以及原生指针的tag。 + +我们再想得复杂一些,如果我们想知道advance的返回类型,那如何做呢? + +首先修改`advance`返回: + +```c++ +// 对外接口 +template +inline typename Iterator_traits::iterator_category +advance(InputIterator &i, Distance n) { + // 通过Ierator_traits询问它的iterator_category是谁 + typedef typename Iterator_traits::iterator_category category; + return __advance(i, n, category()); // 各型别的重载 +} +``` + +紧接着修改`__advance`返回: + +```c++ +// input iterator +template +inline typename Iterator_traits::iterator_category +__advance(inputIterator &i, distance n, + input_iterator_tag) { + std::cout << "input tag" << std::endl; + return input_iterator_tag(); +} + +// output iterator +template +inline typename Iterator_traits::iterator_category +__advance(outputIterator &i, distance n, + output_iterator_tag) { + std::cout << "output tag" << std::endl; + return output_iterator_tag(); +} + +// forward iterator +template +inline typename Iterator_traits::iterator_category +__advance(ForwardIterator &i, Distance n, + forward_iterator_tag) { + std::cout << "forward tag" << std::endl; + return forward_iterator_tag(); +} + +// bidrectional iterator +template +inline typename Iterator_traits::iterator_category +__advance(BidiectionalIterator &i, Distance n, + bidiectional_iterator_tag) { + std::cout << "bidrectional tag" << std::endl; + return bidiectional_iterator_tag(); +} + +// RandomAccess iterator +template +inline typename Iterator_traits::iterator_category +__advance(RandomAccessIterator &i, Distance n, + random_access_iterator_tag) { + std::cout << "randomaccess tag" << std::endl; + return random_access_iterator_tag(); +} +``` + +只需要把`void`修改为相应的萃取机即可。 + +最后测试修改,添加上返回: + +```c++ +int main() { + iterator input; + iterator output; + iterator forward; + iterator bidect; + iterator random; + input_iterator_tag inputIteratorTag = advance(input, 10); + output_iterator_tag outputIteratorTag = advance(output, 10); + forward_iterator_tag forwardIteratorTag = advance(forward, 10); + bidiectional_iterator_tag bidiectionalIteratorTag = advance(bidect, 10); + random_access_iterator_tag randomAccessIteratorTag = advance(random, 10); + int *p = NULL; + random_access_iterator_tag v = advance(p, 10); + return 0; +} +``` + +至此,一个简单的迭代器类型在编译器判别实现完毕。 + +## 2.STL源码剖析Iterator + +在`bits/stl_iterator_base_types.h`中也是如上述所示(实际上,上面就是STL源码的简单版,很接近),来我们一起来看。 + +(1)`tag` + +```c++ + /// Marking input iterators. + struct input_iterator_tag { }; + + /// Marking output iterators. + struct output_iterator_tag { }; + + /// Forward iterators support a superset of input iterator operations. + struct forward_iterator_tag : public input_iterator_tag { }; + + /// Bidirectional iterators support a superset of forward iterator + /// operations. + struct bidirectional_iterator_tag : public forward_iterator_tag { }; + + /// Random-access iterators support a superset of bidirectional + /// iterator operations. + struct random_access_iterator_tag : public bidirectional_iterator_tag { }; +``` + +与我上面用的一样。 + +(2)`iterator_traits`萃取机,里面包含五种,而上面只是实现其中的一种:`iterator_category`。所以在STL中容器与算法之间的桥梁iterator必须包含下面五种 typedef。 + +```c++ +template +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; +}; +``` + +(3)`iterator` + +上面提到的class type为下面的简单版,对比一下,没有啥区别,就是模板参数多了一些,typedef多了。 + +```c++ +template +struct iterator +{ + /// One of the @link iterator_tags tag types@endlink. + typedef _Category iterator_category; + /// The type "pointed to" by the iterator. + typedef _Tp value_type; + /// Distance between iterators is represented as this type. + typedef _Distance difference_type; + /// This type represents a pointer-to-value_type. + typedef _Pointer pointer; + /// This type represents a reference-to-value_type. + typedef _Reference reference; +}; +``` + +至此,iterator与traits特性分析完毕。欢迎与我共同探讨STL源码奥秘,如侯捷老师所说:**源码面前了无秘密。** \ No newline at end of file diff --git a/stl_src/list.md b/stl_src/list.md new file mode 100644 index 0000000..2c7e26f --- /dev/null +++ b/stl_src/list.md @@ -0,0 +1,1278 @@ +# C++ STL源码剖析之双向环形链表list + +## 0. 导语 + +源码对应的版本为**gcc-4.9.1** + +### 1.list + +list为双向环形链表,其结构为: + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_1.png) + +自己绘制的图如下: + +![list_all](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_a.png) + +双向环状链表从节点值为3开始插入,红色框表示最后一个节点(end()指向的节点)。黄色线条表示指向前驱节点,黑色线条表示指向后继节点。 + +### 1.1 list源码 + +#### 1.1.1 类结构 + +```c++ + template > + class list : protected _List_base<_Tp, _Alloc> + { + + } +``` + +`list`继承`_List_base`。 + +#### 1.1.2 双向环形链表实现 + +【**构造函数**】 + +**(1)不带任何元素的list** + +```c++ +explicit +list(const allocator_type &__a) _GLIBCXX_NOEXCEPT: _Base(_Node_alloc_type(__a)) {} +``` + +**(2)带n个元素且赋予初值的list** + +```c++ +explicit list(size_type __n, const value_type &__value = value_type(),const allocator_type &__a = allocator_type()) : _Base(_Node_alloc_type(__a)) +{ _M_fill_initialize(__n, __value); } +``` + +**(3)从一个范围中进行初始化list** + +```c++ +template +list(_InputIterator __first, _InputIterator __last, + const allocator_type &__a = allocator_type()) + : _Base(_Node_alloc_type(__a)) { + // Check whether it's an integral type. If so, it's not an iterator. + typedef typename std::__is_integer<_InputIterator>::__type _Integral; + _M_initialize_dispatch(__first, __last, _Integral()); +} +``` + +【**创建节点**】 + +做的事情:创建一个新的节点并动态分配内存,返回节点。 + +```c++ +_Node *_M_create_node(const value_type &__x) { + _Node *__p = this->_M_get_node(); + __try + { + _M_get_Tp_allocator().construct + (std::__addressof(__p->_M_data), __x); + } + __catch(...) + { + _M_put_node(__p); + __throw_exception_again; + } + return __p; +} +``` + +注意到里面有两个重要的函数`_M_get_node`与`_M_put_node`,我们来查看后发现这些方法来自基类,源码为: + +```c++ +_List_node<_Tp> * _M_get_node() { return _M_impl._Node_alloc_type::allocate(1); } + +void _M_put_node(_List_node<_Tp> *__p) _GLIBCXX_NOEXCEPT +{ _M_impl._Node_alloc_type::deallocate(__p, 1); } +``` + +对应的就是创建节点动态分配内存,若创建过程中抛出异常,则释放内存。 + +【**插入节点**】 + +插入节点包括: + +- 尾部插入n个指定节点值的节点,对应的函数`_M_fill_initialize` + +在list的构造函数中使用: + +```c++ +explicit list(size_type __n, const value_type &__value = value_type(),const allocator_type &__a = allocator_type()) : _Base(_Node_alloc_type(__a)) +{ _M_fill_initialize(__n, __value); } +``` + +- 指定位置插入指定节点值的节点,对应的函数`_M_insert` + +其中大家经常使用的`push_back`与`push_front`底层就是调用`_M_insert`函数。 + +两者函数区别是: + +```c++ +this->_M_insert(end(), __x); // push_back 尾部插入 +this->_M_insert(begin(), __x); // push_front 头部插入 +``` + +- **双向环形链表插入函数**`_M_hook` **(最重要!!!)** + +像前面提到的`push_back`、`push_front`、`_M_insert`,还有`insert`都是使用最基础的双向链表插入函数`_M_hook`实现的。 + +**下面来深入研究一下:** + +其中`_M_fill_initialize`源码如下: + +```c++ +void _M_fill_initialize(size_type __n, const value_type &__x) { + for (; __n; --__n) + push_back(__x); +} +``` + +其中`push_back`源码如下: + +```c++ +void push_back(const value_type &__x) { this->_M_insert(end(), __x); } +``` + +其中`_M_insert`,在指定的位置插入初始值为x的节点。 + +```c++ +void _M_insert(iterator __position, const value_type &__x) { + _Node *__tmp = _M_create_node(__x); + __tmp->_M_hook(__position._M_node); +} +``` + +其中`_M_hook`实现在`gcc-4.9.1/libstdc++-v3/src/c++98/list.cc`中,当然`_List_node_base`的其他函数,例如:`_M_unhook`也在这个文件中。 + +```c++ +// 在指定的位置前插入this指向的节点 +void_List_node_base::_M_hook(_List_node_base* const __position) _GLIBCXX_USE_NOEXCEPT +{ + this->_M_next = __position; + this->_M_prev = __position->_M_prev; + __position->_M_prev->_M_next = this; + __position->_M_prev = this; +} +``` + +所以上述细分为两个函数:我们把上述代码进行总结: + +(1)在指定的位置插入初始值为x的节点 + +```c++ +void _M_insert(iterator __position, const value_type &__x) { + _Node *__tmp = _M_create_node(__x); + __tmp->_M_next = __position; // 第一步 + __tmp->_M_prev = __position->_M_prev; // 第二步 + __position->_M_prev->_M_next = __tmp; // 第三步 + __position->_M_prev = __tmp; // 第四步 +} +``` + +这种插入是在指定位置前插入,(对应到代码就是)例如实现在指定`__position`节点为7前插入节点值9的节点(对应到代码就是__tmp),下面阐述了具体的插入流程。 + +![list_insert](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_insert.png) + +(2)在末尾依次插入n个节点值为x的节点 + +```c++ +void _M_fill_initialize(size_type __n, const value_type &__x) { + for (; __n; --__n) + _M_insert(end(), __x); +} +``` + +对于上述的代码大家或许会联想到`insert`,它有三个。`insert`实现文件在`libstdc++-v3/include/bits/list.tcc`。 + +- **第一:在指定迭代器之前插入指定元素值节点。** + +实现是调用前面的`_M_hook`函数。 + +```c++ +template +typename list<_Tp, _Alloc>::iterator +list<_Tp, _Alloc>:: +#if __cplusplus >= 201103L +insert(const_iterator __position, const value_type& __x) +#else +insert(iterator __position, const value_type& __x) +#endif +{ + _Node* __tmp = _M_create_node(__x); + __tmp->_M_hook(__position._M_const_cast()._M_node); + return iterator(__tmp); +} +``` + +- **第二:在指定迭代器之前插入n个指定节点值的节点。** + +```c++ +void insert(iterator __position, size_type __n, const value_type &__x) { + list __tmp(__n, __x, get_allocator()); + splice(__position, __tmp); +} +``` + +实现是先调用list构造函数,完成创建一个拥有n个指定节点值的list。 + +```c++ +explicit list(size_type __n, const value_type &__value = value_type(),const allocator_type &__a = allocator_type()) : _Base(_Node_alloc_type(__a)) +{ _M_fill_initialize(__n, __value); } +``` + +然后使用`splice`函数完成从另一个list中来插入当前list。 + +```c++ +void splice(iterator __position, list &__x) +{ + if (!__x.empty()) { + _M_check_equal_allocators(__x); + + this->_M_transfer(__position._M_const_cast(), + __x.begin(), __x.end()); + } +} +``` + +其中`_M_transfer`追踪代码,可以知道: + +```c++ +// Moves the elements from [first,last) before position. +void +_M_transfer(iterator __position, iterator __first, iterator __last) { + __position._M_node->_M_transfer(__first._M_node, __last._M_node); +} +``` + +再次分析得到其来自list的基类`_List_node_base`,而`_M_transfer`实现文件在`gcc-4.9.1/libstdc++-v3/src/c++98/list.cc`中: + +```c++ +void _List_node_base:: +_M_transfer(_List_node_base * const __first, + _List_node_base * const __last) _GLIBCXX_USE_NOEXCEPT +{ + if (this != __last) + { + // Remove [first, last) from its old position. + __last->_M_prev->_M_next = this; + __first->_M_prev->_M_next = __last; + this->_M_prev->_M_next = __first; + + // Splice [first, last) into its new position. + _List_node_base* const __tmp = this->_M_prev; + this->_M_prev = __last->_M_prev; + __last->_M_prev = __first->_M_prev; + __first->_M_prev = __tmp; + } +} +``` + +仍然是上述的图: + +![list_all](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_a.png) + +经过前面分析,我们知道`splice`是将上述图的所代表的整个list插入指定迭代器前面,例如,我们想要在下面两个节点前面插入,具体图形步骤如下: + +`this`代表的节点为值为8的节点,下图描述的就是在节点10与节点8中间插入整个list。 + +`__last`代表的是红色框节点,该节点为`end()`指向的节点,我们是不需要该节点的,所以在后面处理中,会把该节点从整个list中去除掉。 + +`__first`代表的是图中节点值为3的节点。 + +**第一步:先将最后一个有效节点,也就是红色框的前一个节点的next指针指向指定的节点8。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step1.png) + +对应代码为: + +``` +__last->_M_prev->_M_next = this; +``` + +**第二步:`_last`的next指针指向自己。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step2.png) + +对应的代码为: + +``` +__first->_M_prev->_M_next = __last; +``` + +**第三步:让指定迭代器之前的节点的nex指向原先list的第一个节点(`__first`)。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step3.png) + +对应的代码为: + +``` +this->_M_prev->_M_next = __first; +``` + +**第四步:保存指定迭代器的前驱节点(对应到哪图中的节点值为10的节点)。** + +``` +_List_node_base* const __tmp = this->_M_prev; +``` + +**第五步:指定迭代器的前驱节点指向原list中实际最后一个节点(end()前一节点)。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step5.png) + +对应的代码为: + +``` +this->_M_prev = __last->_M_prev; +``` + +**第六步:让原list的最后一个节点(end()指向的节点)的prev指向自己。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step6.png) + +对应的代码为: + +``` +__last->_M_prev = __first->_M_prev; +``` + +**第七步:让原list第一个节点的prev指向第四步保存的节点。** + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/step7.png) + +对应的代码为: + +``` +__first->_M_prev = __tmp; +``` + +这样经过以上七步操作,完成了在节点8与节点10之前插入一个list。 + +- **第三:从一个list范围把数据插入到指定迭代器前面。** + +```c++ +template +void +insert(iterator __position, _InputIterator __first, + _InputIterator __last) { + list __tmp(__first, __last, get_allocator()); + splice(__position, __tmp); +} +``` + +原理同上,只不过这个`__tmp`是调用另外一个构造函数。 + +【**删除节点**】 + +- 删除指定节点 + +删除指定节点分为两个,第一个通过迭代器删除,第二个是通过元素值删除。 + +**(1)通过迭代器删除,对应函数为erase** + +其中`pop_front`与`pop_back`、`erase`、`remove`底层实现基于`_M_erase`函数。 + +```c++ +this->_M_erase(begin()); // pop_front 不断删除起点的元素 +this->_M_erase(iterator(this->_M_impl._M_node._M_prev)); // pop_back移除最后一个元素 +``` + +在`libstdc++-v3/include/bits/list.tcc`中: + +```c++ +erase(iterator __position) +#endif +{ + iterator __ret = iterator(__position._M_node->_M_next); + _M_erase(__position._M_const_cast()); + return __ret; +} +``` + +**(2)通过元素值删除,对应函数为remove** + +特殊情况处理,当删除元素的地址与迭代器的地址一样的时候,先保存起来,最后判断保存的迭代器是不是end(),如果不是,就删除掉。底层仍旧是通过`_M_erase`删除。 + +```c++ +template +void list<_Tp, _Alloc>::remove(const value_type& __value) +{ + iterator __first = begin(); + iterator __last = end(); + iterator __extra = __last; + while (__first != __last) + { + iterator __next = __first; + ++__next; + if (*__first == __value) + { + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 526. Is it undefined if a function in the standard changes + // in parameters? + if (std::__addressof(*__first) != std::__addressof(__value)) + _M_erase(__first); + else + __extra = __first; + } + __first = __next; + } + if (__extra != __last) + _M_erase(__extra); +} +``` + +除了这个remove外,还有一个是`remove_if`,根据条件来删除。 + +```c++ +template +template +void list<_Tp, _Alloc>:: +remove_if(_Predicate __pred) +{ + iterator __first = begin(); + iterator __last = end(); + while (__first != __last) + { + iterator __next = __first; + ++__next; + if (__pred(*__first)) + _M_erase(__first); + __first = __next; + } +} + +``` + +对上述的`remove`的if去掉,在里面添加一个判断即可。 + +使用如下: + +```c++ +bool isone(int one) { + return one==2; +} +int main() { + list t; + t={3,4,0,2,0,10,10}; + for(auto i:t) cout<_M_unhook(); + _Node *__n = static_cast<_Node *>(__position._M_node); +#if __cplusplus >= 201103L + _M_get_Node_allocator().destroy(__n); +#else + _M_get_Tp_allocator().destroy(std::__addressof(__n->_M_data)); +#endif + _M_put_node(__n); // 释放内存 +} + +``` + +其中`_M_unhook`实现在`gcc-4.9.1/libstdc++-v3/src/c++98/list.cc`,实现如下: + +```c++ +void _List_node_base::_M_unhook() _GLIBCXX_USE_NOEXCEPT +{ + _List_node_base* const __next_node = this->_M_next; // 第一步:保存后继节点 + _List_node_base* const __prev_node = this->_M_prev; // 第二步:保存前驱节点 + __prev_node->_M_next = __next_node; // 第三步:前驱节点的next指向后继节点 + __next_node->_M_prev = __prev_node; // 第四步:后继节点的prev指向前驱节点 +} +``` + +例如:删除节点值为9的节点,第三与第四步图解: + +![list_erase](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_erase.png) + +**(2)删除一系列元素详细分析** + +```c++ +iterator +#if __cplusplus >= 201103L +erase(const_iterator __first, const_iterator __last) noexcept +#else +erase(iterator __first, iterator __last) +#endif +{ + while (__first != __last) + __first = erase(__first); + return __last._M_const_cast(); +} +``` + +使用erase,删除给定迭代器范围内的数据。 + +**(3)删除所有元素详细分析** + +清空元素,并初始化,回到list默认状态。 + +```c++ +void clear() +_GLIBCXX_NOEXCEPT +{ + _Base::_M_clear(); + _Base::_M_init(); +} +``` + +其中`_M_clear`实现在:`libstdc++-v3/include/bits/list.tcc`中: + +```c++ +_List_base<_Tp, _Alloc>:: +_M_clear() _GLIBCXX_NOEXCEPT +{ + typedef _List_node<_Tp> _Node; + _Node* __cur = static_cast<_Node*>(_M_impl._M_node._M_next); + while (__cur != &_M_impl._M_node) + { + _Node* __tmp = __cur; // 保存节点 + __cur = static_cast<_Node*>(__cur->_M_next); // 往后遍历 + #if __cplusplus >= 201103L + _M_get_Node_allocator().destroy(__tmp); + #else + _M_get_Tp_allocator().destroy(std::__addressof(__tmp->_M_data)); + #endif + _M_put_node(__tmp); // 释放内存 + } +} +``` + +`_M_init`实现,全部指向自己即可。 + +```c++ +void _M_init() +_GLIBCXX_NOEXCEPT +{ + this->_M_impl._M_node._M_next = &this->_M_impl._M_node; + this->_M_impl._M_node._M_prev = &this->_M_impl._M_node; +} +``` + +【**元素访问**】 + +每个都实现了两个版本:引用与常引用。 + +- front 返回第一个元素 + +```c++ +reference front() +_GLIBCXX_NOEXCEPT +{ return *begin(); } +const_reference +front() const +_GLIBCXX_NOEXCEPT +{ return *begin(); } +``` + +- 返回最后一个元素 + +```c++ +reference +back() +_GLIBCXX_NOEXCEPT +{ + iterator __tmp = end(); + --__tmp; + return *__tmp; +} +const_reference +back() const +_GLIBCXX_NOEXCEPT +{ + const_iterator __tmp = end(); + --__tmp; + return *__tmp; +} +``` + +【**算法**】 + +- unique + +从容器中的每个连续的相等元素组中除去除第一个元素外的所有元素。 + +请注意,只有与[列表](http://www.cplusplus.com/list)容器紧邻的元素相比,该元素才从[列表](http://www.cplusplus.com/list)容器中删除。因此,此功能对于排序列表特别有用。 + +```c++ +template +template + void + list<_Tp, _Alloc>:: + unique(_BinaryPredicate __binary_pred) + { + iterator __first = begin(); + iterator __last = end(); + if (__first == __last) + return; + iterator __next = __first; + while (++__next != __last) + { + // 满足条件就删除 + if (__binary_pred(*__first, *__next)) + // 删除 + _M_erase(__next); + else + __first = __next; + __next = __first; + } + } +``` + +举例如下: + +```c++ +// list::unique +#include +#include +#include + +// a binary predicate implemented as a function: +bool same_integral_part (double first, double second) +{ return ( int(first)==int(second) ); } + +// a binary predicate implemented as a class: +struct is_near { + bool operator() (double first, double second) + { return (fabs(first-second)<5.0); } +}; + +int main () +{ + double mydoubles[]={ 12.15, 2.72, 73.0, 12.77, 3.14, + 12.77, 73.35, 72.25, 15.3, 72.25 }; + std::list mylist (mydoubles,mydoubles+10); + + mylist.sort(); // 2.72, 3.14, 12.15, 12.77, 12.77, + // 15.3, 72.25, 72.25, 73.0, 73.35 + + mylist.unique(); // 2.72, 3.14, 12.15, 12.77 + // 15.3, 72.25, 73.0, 73.35 + + mylist.unique (same_integral_part); // 2.72, 3.14, 12.15 + // 15.3, 72.25, 73.0 + + mylist.unique (is_near()); // 2.72, 12.15, 72.25 + + std::cout << "mylist contains:"; + for (std::list::iterator it=mylist.begin(); it!=mylist.end(); ++it) + std::cout << ' ' << *it; + std::cout << '\n'; + + return 0; +} +``` + +上述排序后会删除所有的重复元素,只剩下一个,而不排序只会删除重复且连续的元素。 + +- merge + +merge源码实现采用前面`_M_transfer`函数,假设现在有两个list,分别是list1与list2。list1中的元素与list2中的元素进行比较,如果list1中元素值小于list2中元素值,则对list1迭代器++,不进行任何操作,而如果list1中的元素值大于list2中的元素值,那么每次将list2这个小的元素对应的迭代器塞入`_M_transfer`函数中,通过这个函数完成向list1中刚才比较的迭代器前面插入list2较小的元素,那么最后所有元素都会被插入到list1中。 + +当list1已经遍历完毕,而list2还没有遍历完毕,那么只需要执行一次`_M_transfer`,将list2链表从当前迭代器开始到最后的end插入到list1的末尾即可。 + +```c++ +template +void +list<_Tp, _Alloc>:: +#if __cplusplus >= 201103L +merge(list&& __x) +#else +merge(list& __x) +#endif +{ + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 300. list::merge() specification incomplete + if (this != &__x) + { + _M_check_equal_allocators(__x); + + iterator __first1 = begin(); + iterator __last1 = end(); + iterator __first2 = __x.begin(); + iterator __last2 = __x.end(); + while (__first1 != __last1 && __first2 != __last2) + if (*__first2 < *__first1) + { + iterator __next = __first2; + _M_transfer(__first1, __first2, ++__next); + __first2 = __next; + } + else + ++__first1; + if (__first2 != __last2) + _M_transfer(__last1, __first2, __last2); + } +} +``` + +使用: + +```c++ +int main() { + list l1 = {2,3,5,7}; + list l2 = {1,10,9,5}; + l1.sort(); + l2.sort(); + l1.merge(l2); + for(auto i:l1) cout<(__position._M_const_cast(), + __i._M_const_cast(), __j); +} +``` + +最后调用的是`_M_transfer`。 + +在sort中还有一个函数`swap`,完成两个链表交换,实现代码在`gcc-4.9.1/libstdc++-v3/src/c++98/list.cc`中: + +```c++ +void +_List_node_base::swap(_List_node_base& __x, + _List_node_base& __y) _GLIBCXX_USE_NOEXCEPT +{ + if ( __x._M_next != &__x ) + { + if ( __y._M_next != &__y ) + { + // Both __x and __y are not empty. + std::swap(__x._M_next,__y._M_next); + std::swap(__x._M_prev,__y._M_prev); + __x._M_next->_M_prev = __x._M_prev->_M_next = &__x; + __y._M_next->_M_prev = __y._M_prev->_M_next = &__y; + } + else + { + // __x is not empty, __y is empty. + __y._M_next = __x._M_next; + __y._M_prev = __x._M_prev; + __y._M_next->_M_prev = __y._M_prev->_M_next = &__y; + __x._M_next = __x._M_prev = &__x; + } + } + else if ( __y._M_next != &__y ) + { + // __x is empty, __y is not empty. + __x._M_next = __y._M_next; + __x._M_prev = __y._M_prev; + __x._M_next->_M_prev = __x._M_prev->_M_next = &__x; + __y._M_next = __y._M_prev = &__y; + } +} +``` + +具体的实现思路是,判断两个链表为空还是不为空,然后修改next指针与prev指针。 + +下面来看看强大的sort: + +```c++ +template +void +list<_Tp, _Alloc>:: +sort() { + // Do nothing if the list has length 0 or 1. + if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node + && this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node) { + list __carry; // 辅助链表,用于从a中提取元素以及临时保存两个链表的合并结果 + list __tmp[64]; // 保存着当前每一个归并层次的结果, i号链表保存的元素个数为2的i次方或者0 + list *__fill = &__tmp[0]; // 表示当前最大归并排序的层次,while循环之后__fill变成log2(list.size()) + list *__counter; + + do { + __carry.splice(__carry.begin(), *this, begin()); //把当前链表的第一个节点放在carry链表头 + + for (__counter = &__tmp[0]; + __counter != __fill && !__counter->empty(); + ++__counter) { + __counter->merge(__carry); // 两个有序链表合并 + __carry.swap(*__counter); // 类似于交换链表carry和counter[i]内容 + } + __carry.swap(*__counter); // 类似于交换链表carry和counter[i]内容 + if (__counter == __fill) + ++__fill; + } while (!empty()); + // 每两个进行归并,依次网上,直到最后*(__fill-1)保存最后的排序结果。然后交换到当前list中。 + for (__counter = &__tmp[1]; __counter != __fill; ++__counter) + __counter->merge(*(__counter - 1)); + swap(*(__fill - 1)); + } +} +``` + +上述代码看起来比较难懂,在网上查找后发现,G2.9中: + +```c++ +template +void list :: sort(){ + // 判断链表是否为空或者只有一个元素 + if(node->next == node || link_type(node->next)->next == node){ + return; + } + + list carry; + list counter[64]; + int fill = 0; + while(!empty()){ + carry.splice(carry.begin(), *this, begin()); + int i = 0; + while(i < fill && !counter[i].empty()){ + counter[i].merge(carry); + carry.swap(counter[i++]); + } + carry.swap(counter[i]); + if(i == fill){ + ++fill; + } + } + + for(int i = 1; i < fill; ++i){ + counter[i].merge(counter[i-1]); + } + swap(counter[fill-1]); +} +``` + +对应的外部实现是: + +```c++ +void sortList(list &l) { + if (l.size() <= 1) { + return; + } + list carry; // 辅助链表,用于从a中提取元素以及临时保存两个链表的合并结果 + list counter[64]; // 保存着当前每一个归并层次的结果, i号链表保存的元素个数为2的i次方或者0 + int fill = 0; // 表示当前最大归并排序的层次,while循环之后fill变成log2(a.size()) + + while (!l.empty()) { + carry.splice(carry.begin(), l, l.begin()); // 将链表a中的第一个元素移动至carry开头 + int i = 0; + // 从小往大不断合并非空归并层次直至遇到空层或者到达当前最大归并层次 + while (i < fill && !counter[i].empty()) { + counter[i].merge(carry); // 链表合并,结果链表是有序的,必须保证合并前两个链表是有序的 + carry.swap(counter[i++]); // 链表元素互换 + } + carry.swap(counter[i]); + if (i == fill) { // i到达当前最大归并层次,说明得增加一层 + ++fill; + } + } + + for (int i = 1; i < fill; ++i) { // 将所有归并层次的结果合并得到最终结果counter[fill - 1] + counter[i].merge(counter[i - 1]); + } + l.swap(counter[fill - 1]); +} +``` + +这一块可以参考 + +> https://blog.csdn.net/chenhanzhun/article/details/39337331 + +上面给出了详细的过程图解。 + +我们再次将G4.9转换对应的外部实现: + +```c++ +void sortList1(list &l) { + typedef list list; + if (l.size() <= 1) { + return; + } + list __carry; + list __tmp[64]; + list *__fill = &__tmp[0]; + list *__counter; + do { + __carry.splice(__carry.begin(), l, l.begin()); + for (__counter = &__tmp[0]; + __counter != __fill && !__counter->empty(); + ++__counter) { + __counter->merge(__carry); + __carry.swap(*__counter); + } + __carry.swap(*__counter); + if (__counter == __fill) ++__fill; + } while (!l.empty()); + + for (__counter = &__tmp[1]; __counter != __fill; ++__counter) + __counter->merge(*(__counter - 1)); + + l.swap(*(__fill - 1)); +} +``` + +使用: + +```c++ +int main() { + list l = {7, 5, 8, 1}; + cout << "===============排序前==============" << endl; + for (auto i:l) cout << i << " "; + cout << endl; + sortList1(l); + cout << "===============排序后==============" << endl; + for (auto i:l) cout << i << " "; + cout << endl; + + return 0; +} +``` + +【**操作符重载**】 + +```c++ +template +inline bool +operator==(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { + typedef typename list<_Tp, _Alloc>::const_iterator const_iterator; + const_iterator __end1 = __x.end(); + const_iterator __end2 = __y.end(); + + const_iterator __i1 = __x.begin(); + const_iterator __i2 = __y.begin(); + while (__i1 != __end1 && __i2 != __end2 && *__i1 == *__i2) { + ++__i1; + ++__i2; + } + return __i1 == __end1 && __i2 == __end2; +} +``` + +实现思路是,迭代判断两个迭代器是否都抵达末尾。 + +剩下就是其他的操作符重载,比较简单,就不阐述了。其中`lexicographical_compare`实现在`c++-v3/src/c++98/stl_algobase.h`中,该函数是按照字典序测试[frist1,last1)是否小于[first2,last2)。该函数使用opeartor<或者是comp进行比较。其行为类似于:如果两个序列长度不同,并且短序列和长序列头部完全一样,例如example和examplee.那么,长度大的字典序比短序的大。 + +```c++ + +template +bool lexicographical_compare (InputIterator1 first1, InputIterator1 last1, +InputIterator2 first2, InputIterator2 last2) +{ + while (first1!=last1) + { + if (first2==last2 || *first2<*first1) return false; + else if (*first1<*first2) return true; + ++first1; ++first2; + } + return (first2!=last2); +} +``` + +使用: + +```c++ +int main() { + vector v1{'h','e','l','l','o'}; + vector v2{'h','e','l','l','o','o'}; + vector v3{'h','e','l','m','o'}; + cout<<"v1="; + for(char i:v1) + cout< +inline bool +operator<(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { + return std::lexicographical_compare(__x.begin(), __x.end(), + __y.begin(), __y.end()); +} + +/// Based on operator== +template +inline bool +operator!=(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { return !(__x == __y); } + +/// Based on operator< +template +inline bool +operator>(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { return __y < __x; } + +/// Based on operator< +template +inline bool +operator<=(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { return !(__y < __x); } + +/// Based on operator< +template +inline bool +operator>=(const list<_Tp, _Alloc> &__x, const list<_Tp, _Alloc> &__y) { return !(__x < __y); } +``` + +### 1.2 list基类源码 + +`_list_base`中有一个结构体:`_List_impl`,而`_List_impl`中有一个`List_node_base`。 + +```c++ +class _List_base +{ +protected: + typedef typename _Alloc::template rebind<_List_node<_Tp> >::other + _Node_alloc_type; + + typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type; + + struct _List_impl + : public _Node_alloc_type + { + __detail::_List_node_base _M_node; + + _List_impl() + : _Node_alloc_type(), _M_node() + { } + + _List_impl(const _Node_alloc_type& __a) _GLIBCXX_NOEXCEPT + : _Node_alloc_type(__a), _M_node() + { } + + #if __cplusplus >= 201103L + _List_impl(_Node_alloc_type&& __a) _GLIBCXX_NOEXCEPT + : _Node_alloc_type(std::move(__a)), _M_node() + { } + #endif + }; + + _List_impl _M_impl; +}; + +``` + +最后形成的图就是: + +![list's iterator_design](https://raw.githubusercontent.com/Light-City/cloudimg/master/list_iterator_design.png) + +所以如果求: + +```c++ +sizeof(list)=16 +``` + +原因是: + +`list`的sizeof为1,所以sizeof来源于基类`_list_base`,而`_list_base`中有一个结构体:`_List_impl`,而`_List_impl`中有一个`_List_node_base`. + +我们知道`_List_node_base`,里面有两个指针,在64位上,每个为8字节,共16字节。 + +## 2.list's Iterator剖析 + +### 2.1 iterator + +list的iterator定义 + +```c++ +template +struct _List_iterator +{ + typedef _List_iterator<_Tp> _Self; + typedef _List_node<_Tp> _Node; + + typedef ptrdiff_t difference_type; + typedef std::bidirectional_iterator_tag iterator_category; + typedef _Tp value_type; + typedef _Tp* pointer; + typedef _Tp& reference; + + // The only member points to the %list element. + __detail::_List_node_base* _M_node; + // _List_node(节点的数据部分) -> _List_node_base(前指针与后指针) + + _List_iterator() _GLIBCXX_NOEXCEPT + : _M_node() { } + + explicit + _List_iterator(__detail::_List_node_base* __x) _GLIBCXX_NOEXCEPT + : _M_node(__x) { } + + _Self + _M_const_cast() const _GLIBCXX_NOEXCEPT + { return *this; } + + // The only member points to the %list element. + __detail::_List_node_base* _M_node; +}; + +``` + +内部重载函数: + +```c++ +// Must downcast from _List_node_base to _List_node to get to _M_data. +// 重载*操作符 +reference operator*() const _GLIBCXX_NOEXCEPT +{ + return static_cast<_Node*>(_M_node)->_M_data; +} + +// 重载->操作符 +pointer operator->() const _GLIBCXX_NOEXCEPT +{ + return std::__addressof(static_cast<_Node*>(_M_node)->_M_data); +} + +// 重载前置++操作符 ++i +_Self& operator++() _GLIBCXX_NOEXCEPT +{ + _M_node = _M_node->_M_next; + return *this; +} + +// 重载后置++操作符 i++ +_Self operator++(int) _GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; // 记录原值 *调用的是拷贝构造函数 + _M_node = _M_node->_M_next; // 进行操作 + return __tmp; // 返回原值 +} + +// 重载前置--操作符 --i +_Self& operator--() _GLIBCXX_NOEXCEPT +{ + _M_node = _M_node->_M_prev; + return *this; +} +// 重载后置--操作符 --i +_Self operator--(int) _GLIBCXX_NOEXCEPT +{ + _Self __tmp = *this; + _M_node = _M_node->_M_prev; + return __tmp; +} +// 重载++操作符 +bool operator==(const _Self& __x) const _GLIBCXX_NOEXCEPT +{ + return _M_node == __x._M_node; +} +// 重载!=操作符 +bool operator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT +{ + return _M_node != __x._M_node; +} +``` + +### 2.2 结点设计 + +iterator内部的`_List_node`,这里可以得到继承自`_List_node_base`. + +`_List_node`放数据部分 + +`_List_node_base`放前后指针 + +```c++ +/// An actual node in the %list. +template +struct _List_node : public __detail::_List_node_base +{ + ///< User's data. + _Tp _M_data; + +#if __cplusplus >= 201103L + template + _List_node(_Args&&... __args) +: __detail::_List_node_base(), _M_data(std::forward<_Args>(__args)...) + { } +#endif +}; +``` + +`_List_node_base`代码: + +```c++ +namespace __detail +{ + _GLIBCXX_BEGIN_NAMESPACE_VERSION + /// Common part of a node in the %list. + struct _List_node_base + { + _List_node_base* _M_next; + _List_node_base* _M_prev; + + static void + swap(_List_node_base& __x, _List_node_base& __y) _GLIBCXX_USE_NOEXCEPT; + + void + _M_transfer(_List_node_base* const __first, + _List_node_base* const __last) _GLIBCXX_USE_NOEXCEPT; + + void + _M_reverse() _GLIBCXX_USE_NOEXCEPT; + + void + _M_hook(_List_node_base* const __position) _GLIBCXX_USE_NOEXCEPT; + + void + _M_unhook() _GLIBCXX_USE_NOEXCEPT; + }; + + _GLIBCXX_END_NAMESPACE_VERSION +} // namespace detail +``` + +迭代器在设计的时候,总是保持前闭后开原则,例如iter->begin()指向第一个元素,iter->end()指向实际最后一个元素的下一个元素,故最后的设计刻意在环形list尾部加一个空白结点,用以符合STL前闭后开原则. \ No newline at end of file diff --git a/stl_src/map_multimap.md b/stl_src/map_multimap.md new file mode 100644 index 0000000..fb87269 --- /dev/null +++ b/stl_src/map_multimap.md @@ -0,0 +1,330 @@ +# C++ STL源码剖析之map、multimap、initializer_list +map/multimap 以rb_tree为底层结构,因此有元素自动排序特点,排序的依据是key。 + +map/multimap提供"遍历"操作及iterators。按正常规则(++iter)遍历,便能够获得排序状态。 + +我们无法使用map/multimap的iterators改变元素的key(因为key有其严谨排列规则),但可以用它来改变元素的data。因此map/multimap内部自动将用户指定的key type设定为const,如此便能进制用户对元素key的赋值。 + +map元素的key必须独立无二,因此其insert使用的是rb_tree的`_M_insert_unique()`,而multimap元素的key可以重复,因此其insert使用的是rb_tree的`_M_insert_equal()`。 + +对于本节,我们将从下面几个内容阐述: + +- map的key为key,value为key+data,与set是不同的,set是key就是value,value就是key。 +- map的key不可修改,map与multimap的插入调用函数不同,影响了其key是否对应value。 +- initializer_list使用 +- map有`[]`操作符,而multimap没有`[]`操作符。 + + +## 1.map + +> key为key,value为key+data + +下面map中我们可以看到value_type为一个pair。 + +```cpp +template , +typename _Alloc = std::allocator > > +class map +{ +public: + typedef _Key key_type; + typedef _Tp mapped_type; + typedef std::pair value_type; + typedef _Compare key_compare; + typedef _Alloc allocator_type; +private: + // key为key,value为key+data + typedef _Rb_tree, + key_compare, _Pair_alloc_type> _Rep_type; + + /// The actual tree structure. + _Rep_type _M_t; +}; +``` +![map_data.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/map_data.png) + +上述默认的仿函数为`_Select1st`,我们在`stl_function`中看到源码如下: + +```cpp +template +struct _Select1st +: public unary_function<_Pair, typename _Pair::first_type> +{ + typename _Pair::first_type& + operator()(_Pair& __x) const + { return __x.first; } +}; +``` +我们看到上述的`_Select1st`为一个struct,怎么能说它是仿函数呢? +因为里面重载了一个()操作符,哈哈~ + +下面我们来自己实现一个: + +```cpp +template +struct mySelect1st + : public unary_function<_T1, typename _T1::first_type> +{ + template + typename _T2::first_type& + operator()(_T2& __x) const + { return __x.first; } +}; +int main() { + typedef pair value_type; + _Rb_tree, less> it; + it._M_insert_unique(make_pair(1,3)); + it._M_insert_unique(make_pair(3,6)); + for(auto each:it) + cout< key不能改,data可以改 + +上述源码中:自动为key添加一个const,所以key不能改。 +```cpp +typedef std::pair value_type; +``` +## 2.insert + +> insert里面采用`_M_insert_unique` + +insert的几种方法: + +(1) 插入 pair + +```cpp +std::pair insert(const value_type& __x) +{ return _M_t._M_insert_unique(__x); } +``` + +map里面 + +(2) 在指定位置,插入pair +```cpp +iterator insert(iterator __position, const value_type& __x) +{ return _M_t._M_insert_equal_(__position, __x); } +``` + +(3) 从一个范围进行插入 + +```cpp +template +void +insert(_InputIterator __first, _InputIterator __last) +{ _M_t._M_insert_equal(__first, __last); } +``` +(4)从list中插入 + +```cpp +void +insert(initializer_list __l) +{ this->insert(__l.begin(), __l.end()); } +``` + +针对最后一个insert,里面有个`initializer_list`,举个例子大家就知道了。 + + +## 3.initializer_list使用 + +> 实际编程实践 + + +```cpp +vector v={1,2,3}; // 底层调用vector的构造函数 +v={2,5,6}; // 底层调用vector的=操作符 +initializer_list ll={4,5,6}; +v.insert(v.begin(),ll); // 底层调用下面insert函数 +for(auto x:v) cout< vv(ll); // 底层调用vector的构造函数 +vector city{"Berlin", "New York", "London", "Cairo","Tokyo", "Cologne"}; +``` +针对这个vector初始化,大家很熟悉了,为何可以这样初始化呢? +我们看一下vector源码: + +```cpp + vector& +operator=(initializer_list __l) +{ + this->assign(__l.begin(), __l.end()); + return *this; +} + +iterator +insert(const_iterator __position, initializer_list __l) +{ return this->insert(__position, __l.begin(), __l.end()); } + +vector(initializer_list __l, +const allocator_type& __a = allocator_type()) +: _Base(__a) +{ +_M_range_initialize(__l.begin(), __l.end(), + random_access_iterator_tag()); +} +``` + +因为它的构造函数里面,参数有个`initializer_list`,因此我们可以针对这个对map进行使用。 +但是map没有类似的构造,它也应用在map构造函数,insert与`=`处,跟上面是一样的,都是三处,哈哈~ + +使用`initializer_list`三处: + +```cpp +// map构造 +map(initializer_list __l, const allocator_type& __a) +: _M_t(_Compare(), _Pair_alloc_type(__a)) +{ _M_t._M_insert_unique(__l.begin(), __l.end()); } +// =操作符重载 +map& +operator=(initializer_list __l) +{ + this->clear(); + this->insert(__l.begin(), __l.end()); + return *this; +} +// insert插入 +void +insert(std::initializer_list __list) +{ insert(__list.begin(), __list.end()); } +``` +> 实际编程实践 + +map使用`initializer_list`(set使用一样): + +```cpp +// 这里要注意,pair的first参数必[须是const +initializer_list> l = {{"hello", 1}, {"world", 2}}; +map mm(l); // map构造函数 +map m2{{"hello", 1}, {"world", 2}}; // map构造函数 +map m1={{"hello", 1}, {"world", 2}}; // map构造函数 +m1 = {{"h", 1}, {"w", 3}}; // 底层调用map的=操作符 +map mp; +mp.insert(l); // insert插入[ +``` + +上述会引出另一个问题: + +```cpp +map m1={{"hello", 1}, {"world", 2}}; // map构造函数 +m1 = {{"h", 1}, {"w", 3}}; // 底层调用map的=操作符 +``` +这两个为何一个调用的构造,一个调用=操作符呢? + +在初始化的时候,定义及赋值的时候就直接调用构造,后面再次赋值,就是先调用拷贝构造(有可能会被编译器优化),再调用=操作符。 + + +> 实例分析 + +下面用一个具体实例来分析一下: + +```cpp +template +class Foo +{ +public: + Foo(initializer_list<_Tp> &list) + { + cout << "Foo(initializer_list<_Tp> &list)"<< endl; + } + + Foo(int) + { + cout << "Foo(int )"<< endl; + } + + Foo(const Foo& f) + { + cout << "调用了拷贝构造函数"<< endl; + } + Foo& operator=(initializer_list<_Tp> __l) + { + cout<<"调用了=操作符重载"< foo=ll; +foo={1,2}; +``` +编译器未被优化的结果: + +``` +Foo(initializer_list<_Tp> &list) +调用了=操作符重载 +``` +我们通过禁用编译器优化:`g++ -o rb rbtree.cpp -std=c++11 -fno-elide-constructors` + +``` +Foo(initializer_list<_Tp> &list) +调用了拷贝构造函数 +调用了=操作符重载 +``` + +## 4.multimap + +同map一样multimap不允许修改key。但是插入使用的是`_M_insert_equal`。 +```cpp +template , +typename _Alloc = std::allocator > > +class multimap +{ +public: + typedef std::pair value_type; +} +``` +下面使用multimap与rbtree两种方式来联系。 +```cpp +multimap c; +c.insert(make_pair(1,"asdqw")); +c.insert(make_pair(1,"qweq")); + +for(auto each:c) cout< valueT; +_Rb_tree, less> mulm; +mulm._M_insert_equal(make_pair(2,"abc")); +mulm._M_insert_equal(make_pair(2,"cde")); +for(auto each:mulm) +cout<first is greater than or equivalent to __k. + if (__i == end() || key_comp()(__k, (*__i).first)) + #if __cplusplus >= 201103L + __i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct, + std::tuple(__k), + std::tuple<>()); + #else + __i = insert(__i, value_type(__k, mapped_type())); + #endif + return (*__i).second; // 返回key的value,此value为传递进map的第二个参数。 +} +``` + +但是multimap没有`[]`操作符!!! + +我们思考一下,因为multimap会根据key,有可能会对应多个value,那如果我们通过`[]`获取对应key的value,此时到底获取的是哪个value呢,所以在STL源码中没有重载这个操作符! + diff --git a/stl_src/myhashtable.md b/stl_src/myhashtable.md new file mode 100644 index 0000000..f9dfb8b --- /dev/null +++ b/stl_src/myhashtable.md @@ -0,0 +1,397 @@ +# 从0到1打牢算法基础之手写一个哈希表 + +## 0.导语 + + +目的:手写实现一个哈希表,采用拉链法构建,每个hash(key)对应的是一个红黑树。 + +看起来很简单,但可以学到很多东西。实现语言:C++。 + +为了打牢算法基础,github开了个仓库,来完成后面算法基础的应用与实现,地址: + +> https://github.com/Light-City/algPratice + +也可以点击原文阅读。 + +## 1.简易版哈希表 + + +我们将哈希表封装在一个类中,完成遍历的定义与声明以及构造、析构的实现: + +```cpp +template +class HashTable { +private: + const static int upperTol = 3; + const static int lowerTol = 1; + const static int initCapacity = 1; + map **hashtable; + int M; + int size; +public: + /** + * 传参构造 + * @param M + */ + HashTable(int M) : M(M), size(0) { + // 这里的括号是为了初始化为0,这就可以不用写下面的代码,当然在后面add之类的操作,就不需要动态分配内存. + // this->hashtable = new map *[M](); + this->hashtable = new map *[M]; + for (int i = 0; i < M; i++) { + this->hashtable[i] = new map; + } + } + + /** + * 默认构造 + */ + HashTable() { + HashTable(initCapacity); + } + + /** + * 析构函数,释放内存 + */ + ~HashTable() { + free(M); + } +private: + /** + * 释放内存 + * @param M + */ + void free(int M) { + for (int i = 0; i < M; i++) { + if (hashtable[i]) + delete hashtable[i]; + } + delete[]hashtable; + +}; +``` + +对于哈希表实现,里面有一个比较重要的哈希函数,这里我们先自己定义一个: +```cpp +/** +* 哈希函数 +* @param key +* @return +*/ +int hashFunc(Key key) { + std::hash h; + return (h(key) & 0x7fffffff) % M; +} +``` +这里使用std的hash得到值之后,将其`&`上`0x7fffffff`,去掉高位的负号,转为正数,然后余上M。 + + +现在有了这些我们来实现一下它的增删改查。 + +> 增操作 + +底层采用的是红黑树,插入是使用insert方法,通过构造一个pair来完成。 +而当key存在的时候,更新值即可,对于更新这一块,如果直接使用insert是不起作用的,比如下面测试: + +```cpp + map m{{"a",1},{"b",2}}; +for(auto i:m) cout<count(key) == 0) { + if (hashtable[hashFunc(key)] == NULL) + hashtable[hashFunc(key)] = new map; + hashtable[hashFunc(key)]->insert(make_pair(key, value)); + size++; + if (size >= maxCapacity()) + resize(2 * M); + } else { + // 否则,修改value. + hashtable[hashFunc(key)]->erase(key); + hashtable[hashFunc(key)]->insert(make_pair(key, value)); + } +} +``` +> 删操作 + +如果key存在,就删除,size--,否则返回失败标记。 +```cpp +/** +* 移除Key +* @param key +* @return 0 success -1 fail +*/ +Value remove(Key key) { + Value ret = -1; + // 是否包含key,若包含key,则直接删除 + if (contains(key)) { + hashtable[hashFunc(key)]->erase(key); + size--; + // if (size == 0) delete hashtable[hashFunc(key)]; // 可以添加这行来动态减少内存 + ret = 0; + // initCapacity 保证不会越界 + if (size < minCapacity() && M / 2 >= initCapacity) resize(M / 2); + } + return ret; +} +``` +> 改操作 + +前面提到过,这里就直接放代码。 + +```cpp +/** +* 重设value +* @param key +* @param value +*/ +void set(Key key, Value value) { + // key不存在 + if (!contains(key)) + hrow "key not exists!"; + // 修改value + hashtable[hashFunc(key)]->erase(key); + hashtable[hashFunc(key)]->insert(make_pair(key, value)); +} +``` + +> 查操作 + +获取key对应的value。 + +```cpp +/** +* 获取key对应的value +* @param key +* @return +*/ +Value get(Key key) { + if (contains(key)) + return hashtable[hashFunc(key)]->at(key); + return 0; +} +``` + +最后,上面有`contains`与`resize`等函数未提。 + +> key存在与否 + +首先contains函数实现,就是判断key存在与否: + +```cpp +/** + * 是否包含key + * @param key + * @return + */ +bool contains(Key key) { + return hashtable[hashFunc(key)] == NULL || this->hashtable[hashFunc(key)]->count(key) == 0 ? false : true; +} +``` + +> 获取size + +```cpp +/** +* 获取哈希表元素个数 +* @return +*/ +int getSize() { + return size; +} +``` + +> 最大容量与最小容量 + +```cpp +/** + * 最大容量 + * @return + */ +Value maxCapacity() { + return M * upperTol; +} + +/** + * 最小容量 + * @return + */ +Value minCapacity() { + return M * lowerTol; +} +``` + +> resize函数 + +完成动态调整内存,将原来内存中的内容拷贝到新分配的空间,释放原空间! + +```cpp +/** +* 动态调整内存,保证时间复杂度O(1)查找 +* 把扩容后的操作,平摊到前面每次操作,时间复杂度O(2),那就是O(1)了 +* @param newM +*/ +void resize(int newM) { + cout << "resize " << newM << endl; + map **newHashTable = new map *[newM]; + for (int i = 0; i < newM; i++) { + newHashTable[i] = new map; + } + int oldM = M; + this->M = newM; + for (int i = 0; i < oldM; i++) { + map m = *(hashtable[i]); + for (auto p:m) + newHashTable[hashFunc(p.first)]->insert(make_pair(p.first, p.second)); + } + + free(oldM); + this->hashtable = newHashTable; +} + +``` + +> 重载<< 操作符 + +声明: +```cpp +private: + template + // 重载<<操作符 + friend ostream &operator<<(ostream &out, HashTable &hashTable); +``` +定义: +```cpp +template +ostream &operator<<(ostream &out, HashTable &hashTable) { + hashTable.print(); + return out; +} +``` + +至此,上述哈希表实现完毕,现在来测试: + + +```cpp +#include "hash.h" +#include +int main() { + + vector words{"java", "c++", "c", "c++", "c#", "python", "ruby", "python", + "c", "c", "c++", "java", "c++", "rust", "python"}; + HashTable ht(1); + for (string word : words) { + if (ht.contains(word)) { + ht.set(word, ht.get(word) + 1); + } else { + ht.add(word, 1); + } + } + cout<> c; + for (long i = 0; i < 100000; i++) + c.push(i+1); + cout << "stack.size()= " << c.size() << endl; + cout << "stack.top()= " << c.top() << endl; + c.pop(); + cout << "stack.size()= " << c.size() << endl; + cout << "stack.top()= " << c.top() << endl; + cout << "use stack milli-seconds : " << (clock() - timeStart) << endl; + timeStart=clock(); + stack> c1; + for (long i = 0; i < 100000; i++) + c1.push(i+1); + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.top()= " << c1.top() << endl; + c1.pop(); + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.top()= " << c1.top() << endl; + cout << "use stack milli-seconds : " << (clock() - timeStart) << endl; + + // vector可以作为stack的底层容器 + stack> c2; + for (long i = 0; i < 100000; i++) + c2.push(i+1); + cout << "stack.size()= " << c2.size() << endl; + cout << "stack.top()= " << c2.top() << endl; + c2.pop(); + cout << "stack.size()= " << c2.size() << endl; + cout << "stack.top()= " << c2.top() << endl; + cout << "use stack milli-seconds : " << (clock() - timeStart) << endl; +} +``` + + +## 2.queue + +在queue的源码中我们关注两点: +- 默认`_Sequence`为`deque` +- 内部函数实现是调用`_Sequence`对应容器的函数。 + +```cpp +template > +class queue +{ +public: + typedef typename _Sequence::value_type value_type; + typedef typename _Sequence::reference reference; + typedef typename _Sequence::const_reference const_reference; + typedef typename _Sequence::size_type size_type; + typedef _Sequence container_type; + +protected: + + _Sequence c; + +public: + + void push(const value_type& __x) + { c.push_back(__x); } + + void pop() + { + __glibcxx_requires_nonempty(); + c.pop_front(); + } +} +``` +其对应的UML类图如下: + +![queue_.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/queue_.png) + +同理,优先队列则是使用`vector`作为默认容器。 + +```cpp +template, +typename _Compare = less > +class priority_queue +{ +public: + typedef typename _Sequence::value_type value_type; + typedef typename _Sequence::reference reference; + typedef typename _Sequence::const_reference const_reference; + typedef typename _Sequence::size_type size_type; + typedef _Sequence container_type; + +protected: + // See queue::c for notes on these names. + _Sequence c; + _Compare comp; + +public: + reference + top() + { + __glibcxx_requires_nonempty(); + return c.front(); + } + + void + push(const value_type& __x) + { + c.push_back(__x); + std::push_heap(c.begin(), c.end(), comp); + } + +} +``` + + +![priority_queue.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/priority_queue.png) + + +测试这两个容器配接器支持的底层容器: + +> queue + + +对于queue底层容器可以是`deque`,也可以是`list`,但不能是`vector`,`map`,`set`,使用默认的deque效率在插入方面比其他容器作为底层要快! + +```cpp +int test_queue() { + cout<<"============test_queue============="<> c; + for (long i = 0; i < 100000; i++) { + c.push(i+1); + } + cout << "stack.size()= " << c.size() << endl; + cout << "stack.front()= " << c.front() << endl; + c.pop(); + cout << "stack.size()= " << c.size() << endl; + cout << "stack.front()= " << c.front() << endl; + cout << "use list milli-seconds : " << (clock() - timeStart) << endl; + + timeStart=clock(); + queue> c1; + for (long i = 0; i < 100000; i++) { + c1.push(i+1); + } + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.front()= " << c1.front() << endl; + c1.pop(); + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.front()= " << c1.front() << endl; + + cout << "use deque milli-seconds : " << (clock() - timeStart) << endl; +} +``` + +> priority_queue + +对于优先队列来说,测试结果发现,采用`deque`要比默认的`vector`插入速度快! +底层支持vector、deque容器,但不支持list、map、set。 + +```cpp +int test_priority_queue() { + cout<<"============test_priority_queue============="<> c1; + for (long i = 0; i < 100000; i++) { + c1.push(i+1); + } + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.top()= " << c1.top() << endl; + c1.pop(); + cout << "stack.size()= " << c1.size() << endl; + cout << "stack.top()= " << c1.top() << endl; + + cout << "use deque milli-seconds : " << (clock() - timeStart) << endl; + + + priority_queue> c2; + for (long i = 0; i < 100000; i++) + c2.push(i+1); + cout << "stack.size()= " << c2.size() << endl; + cout << "stack.top()= " << c2.top() << endl; + c2.pop(); + cout << "stack.size()= " << c2.size() << endl; + cout << "stack.top()= " << c2.top() << endl; + cout << "use stack milli-seconds : " << (clock() - timeStart) << endl; +} +``` + +因此,stack、queue、priority_queue不被称为容器, 把它称为容器配接器。 \ No newline at end of file diff --git a/stl_src/rb_tree.md b/stl_src/rb_tree.md new file mode 100644 index 0000000..a57d997 --- /dev/null +++ b/stl_src/rb_tree.md @@ -0,0 +1,621 @@ +# C++ STL源码剖析之红黑树 + +## 0.导语 + +在STL源码中有两段话,简单翻译后如下: + +STL中Red-black tree(红黑树)class,用来当做SLT关系式容器(如set,multiset,map, +multimap).里面所用的insertion和deletion方法以 +《Introduction to Algorithms》一书为基础,但是有以下两点不同: + +(1)header不仅指向root,也指向红黑树的最左节点,以便用常数时间实现begin(),并且也指向红黑树的最右边节点,以便 +set相关泛型算法(如set_union等等)可以有线性时间表现. + +(2)当要删除的节点有两个子节点时,其后继节点连接到其位置,而不是被复制,因此,唯一使无效的迭代器是引用已删除节点的迭代器。 + +上述话翻译成图,如下,相比于普通的红黑树多了一个header节点,并且为红色。普通的红黑树是以100节点开始的,且满足下面五条性质: +- 每个节点或是红色的,或是黑色的. +- 根节点是黑色的. +- 每个叶节点(NULL)是黑色的. +- 如果一个节点是红色的,则它的两个孩子节点都是黑色的. +- 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点. + +当然这里的rb_tree也是一样满足这几条性质,迭代器的begin指向红黑树根节点,也就是header的父亲,而end指向header节点。 + +![st.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rbt.png)。 +图中省略号表示节点没有画完,还有其他节点,所以省略。 + +## 1.红黑树节点基类 + +红黑树基类,非常简单,在文件开头定义了颜色标记。 + +基类中包含了指向自己的指针,分别定义了left、right、parent,同时包含了一个颜色标记常量,而里面有两个核心函数,目的是获取红黑树中最小节点与最大节点。 +我们知道对于二分搜索树获取最小节点就是左子树一直往下搜,最大节点就是右子树一直往下搜即可。 + +```cpp +// 颜色标记 +enum _Rb_tree_color { _S_red = false, _S_black = true }; + +// 基类 +struct _Rb_tree_node_base +{ +// typedef重命名 +typedef _Rb_tree_node_base* _Base_ptr; + +// 颜色 +_Rb_tree_color _M_color; +// 指向父亲 +_Base_ptr _M_parent; +// 指向左孩子 +_Base_ptr _M_left; +// 指向右孩子 +_Base_ptr _M_right; + +// 求红黑树的最小节点 +static _Base_ptr +_S_minimum(_Base_ptr __x) _GLIBCXX_NOEXCEPT +{ + while (__x->_M_left != 0) __x = __x->_M_left; + return __x; +} + +// 求红黑树最大节点 +static _Base_ptr +_S_maximum(_Base_ptr __x) _GLIBCXX_NOEXCEPT +{ + while (__x->_M_right != 0) __x = __x->_M_right; + return __x; +} + +}; + +``` +## 2.红黑树节点 + +红黑树节点继承自红黑树基类。 +```cpp +template +struct _Rb_tree_node : public _Rb_tree_node_base +{ + typedef _Rb_tree_node<_Value>* _Link_type;//节点指针,指向数据节点 + _Value _M_value_field;//节点数据域,即关键字 +}; +``` + +## 3.红黑树迭代器 + +红黑树迭代器里面有一个红黑树基类成员,然后通过该成员进行迭代器的相关操作。 +同时,我们可以知道该迭代器属于`bidirectional_iterator_tag`。 + +里面也包含了萃取机相关需要的typedef。 + +```cpp +template +struct _Rb_tree_iterator +{ + typedef _Tp value_type; + typedef _Tp& reference; + typedef _Tp* pointer; + + typedef bidirectional_iterator_tag iterator_category; + typedef ptrdiff_t difference_type; + + typedef _Rb_tree_iterator<_Tp> _Self; + typedef _Rb_tree_node_base::_Base_ptr _Base_ptr; + typedef _Rb_tree_node<_Tp>* _Link_type; + + _Base_ptr _M_node; +}; + +``` + +获取数据 + + +```cpp +reference +operator*() const _GLIBCXX_NOEXCEPT +{ return *static_cast<_Link_type>(_M_node)->_M_valptr(); } + +pointer +operator->() const _GLIBCXX_NOEXCEPT +{ return static_cast<_Link_type> (_M_node)->_M_valptr(); } + +``` + +重载++操作符 + +```cpp +_Self& +operator++() _GLIBCXX_NOEXCEPT +{ + _M_node = _Rb_tree_increment(_M_node); + return *this; +} +``` +而`_Rb_tree_increment`底层是`local_Rb_tree_increment`,如下实现: + +```cpp +static _Rb_tree_node_base * +local_Rb_tree_increment( _Rb_tree_node_base* __x ) throw () +{ + if ( __x->_M_right != 0 ) /* 存在右子树,那么下一个节点为右子树的最小节点 */ + { + __x = __x->_M_right; + while ( __x->_M_left != 0 ) + __x = __x->_M_left; + }else { + /* 不存在右子树,那么分为两种情况:自底往上搜索,当前节点为父节点的左孩子的时候,父节点就是后继节点; */ +/* 第二种情况:_x为header节点了,那么_x就是最后的后继节点. 简言之_x为最小节点且往上回溯,一直为父节点的右孩子,直到_x变为父节点,_y为其右孩子 */ + _Rb_tree_node_base *__y = __x->_M_parent; + while ( __x == __y->_M_right ) + { + __x = __y; + __y = __y->_M_parent; + } + if ( __x->_M_right != __y ) + __x = __y; + } + return (__x); +} +``` +重载--操作符: + + +```cpp +_Self& +operator--() _GLIBCXX_NOEXCEPT +{ + _M_node = _Rb_tree_decrement(_M_node); + return *this; +} +``` +同理,而`_Rb_tree_decrement`底层是`local_Rb_tree_decrement`,如下实现: + +```cpp +static _Rb_tree_node_base * +local_Rb_tree_decrement( _Rb_tree_node_base * __x ) +throw () +{ +/* header节点 */ + if ( __x->_M_color == + _S_red + && __x + ->_M_parent->_M_parent == __x ) + __x = __x->_M_right; + else if ( __x->_M_left != 0 ) /* 左节点不为空,返回左子树中最大的节点 */ + { + _Rb_tree_node_base *__y = __x->_M_left; + while ( __y->_M_right != 0 ) + __y = __y->_M_right; + __x = __y; + }else { +/* 自底向上找到当前节点为其父节点的右孩子,那么父节点就是前驱节点 */ + _Rb_tree_node_base *__y = __x->_M_parent; + while ( __x == __y->_M_left ) + { + __x = __y; + __y = __y->_M_parent; + } + __x = __y; + } + return + (__x); +} +``` + + +重载==与!=操作符,直接判断节点指针是否相等。 + +```cpp +bool +operator==(const _Self& __x) const _GLIBCXX_NOEXCEPT +{ return _M_node == __x._M_node; } + +bool +operator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT +{ return _M_node != __x._M_node; } + +``` + + + +其他重要函数,黑节点统计: + +```cpp +unsigned int +_Rb_tree_black_count(const _Rb_tree_node_base *__node, + const _Rb_tree_node_base *__root) throw() { + if (__node == 0) + return 0; + unsigned int __sum = 0; + do { + if (__node->_M_color == _S_black) + ++__sum; + if (__node == __root) + break; + __node = __node->_M_parent; + } while (1); + return __sum; +} +``` +后面来阐述最重要的插入操作。 + +## 4.红黑树操作 + +比较重要的是,里面使用节点基类来声明了一个指针。还包含了一个`_Rb_tree_impl`用来对红黑树初始化操作与内存管理操作。里面还包含了两种迭代器,一个rbtree,另一个是reverse,说明支持rbegin,rend操作。 +```cpp +template > +class _Rb_tree +{ + +protected: + typedef _Rb_tree_node_base* _Base_ptr; + + template + struct _Rb_tree_impl : public _Node_allocator + { + _Key_compare _M_key_compare; + _Rb_tree_node_base _M_header; + size_type _M_node_count; // Keeps track of size of tree. + + _Rb_tree_impl() + : _Node_allocator(), _M_key_compare(), _M_header(), + _M_node_count(0) + { _M_initialize(); } + + _Rb_tree_impl(const _Key_compare& __comp, const _Node_allocator& __a) + : _Node_allocator(__a), _M_key_compare(__comp), _M_header(), + _M_node_count(0) + { _M_initialize(); } + + private: + void + _M_initialize() + { + this->_M_header._M_color = _S_red; + this->_M_header._M_parent = 0; + this->_M_header._M_left = &this->_M_header; + this->_M_header._M_right = &this->_M_header; + } + + }; +public: + typedef _Rb_tree_iterator iterator; + typedef std::reverse_iterator reverse_iterator; +private: +_Rb_tree_impl<_Compare> _M_impl; +}; +``` +> 获取红黑树根节点、最左与最右节点 + +回到一开始的图: +![rbt.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rbt.png)。 + + +```cpp +// 图中100 节点 + _Base_ptr& +_M_root() _GLIBCXX_NOEXCEPT +{ return this->_M_impl._M_header._M_parent; } + +// 图中most left标记 +_Base_ptr& +_M_leftmost() _GLIBCXX_NOEXCEPT +{ return this->_M_impl._M_header._M_left; } + + +// 图中most right标记 + _Base_ptr& +_M_rightmost() _GLIBCXX_NOEXCEPT +{ return this->_M_impl._M_header._M_right; } + _Link_type + + // 图中begin()标记 +_M_begin() _GLIBCXX_NOEXCEPT +{ return static_cast<_Link_type>(this->_M_impl._M_header._M_parent); } + +// 图中end()标记 +_Link_type +_M_end() _GLIBCXX_NOEXCEPT +{ return reinterpret_cast<_Link_type>(&this->_M_impl._M_header); } + +``` +我们再看代码是不是非常清晰! + + + + +## 5.红黑树插入 + + + +### 5.1 旋转过程 + +左旋转是将该节点的右节点设置为它的父节点,该节点将变成刚才右节点的左孩子 + +直接看源码中的图与代码对比即可。 + +在`tree.cc`源码中实现函数为`local_Rb_tree_rotate_left`与`local_Rb_tree_rotate_right`。 +下面我们将源码进行剖析成比较容易理解的代码,具体见注释。 +大家会发现函数名与变量名与源码不同,是因为下面是当时自己实现的,但是不影响源码阅读,就直接拿来对比了。 + +```cpp +/** +* 当前节点的左旋转过程 +* 将该节点的右节点设置为它的父节点,该节点将变成刚才右节点的左孩子 +* @param _x +*/ +// _x _y +// / \ 左旋转 / \ +// T1 _y ---------> _x T3 +// / \ / \ +// T2 T3 T1 T2 +void leftRotate(Node *_x) { + // step1 处理_x的右孩子 + // 右节点变为_x节点的父亲节点,先保存一下右节点 + Node *_y = _x->right; + // T2变为node的右节点 + _x->right = _y->left; + if (NULL != _y->left) + _y->left->parent = _x; + + // step2 处理_y与父亲节点关系 + _y->parent = _x->parent; // 原来_x的父亲变为_y的父亲 + // 说明原来_x为root节点,此时需要将_y设为新root节点 + // 或者判断NULL == _y->parent + if (_x == root) + root = _y; + else if (_x == _x->parent->left) // 原_x的父节点的左孩子连接新节点_y + _x->parent->left = _y; + else // 原_x的父节点的右孩子连接新节点_y + _x->parent->right = _y; + + // step3 处理_x与_y关系 + _y->left = _x; // _y的左孩子为_x + _x->parent = _y; // _x的父亲是_y +} +``` + +同理,右旋转如下: +```cpp +// _x _y +// / \ 右旋转 / \ +// _y T2 -------------> T0 _x +// / \ / \ +// T0 T1 T1 T2 +void rightRotate(Node *_x) { + // step1 处理_x的左孩子 + // 左节点变为_x节点的父亲节点,先保存一下左节点 + Node *_y = _x->left; + // T1变为_x的左孩子 + _x->left = _y->right; + if (NULL != _y->right) + _y->right->parent = _x; + + // step2 处理_y与父节点之间的关系 + // 或者判断_x->parent==NULL + if (_x == root) + root = _y; + else if (_x == _x->parent->right) + _x->parent->right = _y; + else + _x->parent->left = _y; + + // step3 处理_x与_y关系 + _y->right = _x; // _y的右孩子为_x + _x->parent = _y; // _x的父亲是_y +} +``` + + +case 1.1: 父节点为红色且其叔叔节点也为红色,则将父亲、叔叔置为黑色,祖父置为红色。 + + +![rb_1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb_1.png) + +case 1.2 若无叔叔节点或者其叔叔节点为黑色分为下面两种: + +情况1.2.1:x的叔叔节点y是黑色且x是一个右孩子 + +![rb1.2.1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb1.2.1.png) + +情况1.2.2:x的叔叔节点y是黑色且x是一个左孩子 + +![rb1.2.2.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb1.2.2.png) + +对应源代码中: +```cpp +_Rb_tree_node_base *const __y = __xpp->_M_right; // 得到叔叔节点 +if (__y && __y->_M_color == _S_red) // case1: 叔叔节点存在,且为红色 +{ + /** + * 解决办法是:颜色翻转,父亲与叔叔的颜色都变为黑色,祖父节点变为红色,然后当前节点设为祖父,依次网上来判断是否破坏了红黑树性质 + */ + __x->_M_parent->_M_color = _S_black; // 将其父节点改为黑色 + __y->_M_color = _S_black; // 将其叔叔节点改为黑色 + __xpp->_M_color = _S_red; // 将其祖父节点改为红色 + __x = __xpp; // 修改_x,往上回溯 +} else { // 无叔叔或者叔叔为黑色 + if (__x == __x->_M_parent->_M_right) { // 当前节点为父亲节点的右孩子 + __x = __x->_M_parent; + local_Rb_tree_rotate_left(__x, __root); // 以父节点进行左旋转 + } + // 旋转之后,节点x变成其父节点的左孩子 + __x->_M_parent->_M_color = _S_black; // 将其父亲节点改为黑色 + __xpp->_M_color = _S_red; // 将其祖父节点改为红色 + local_Rb_tree_rotate_right(__xpp, __root); // 以祖父节点右旋转 +} + +``` + +另外一个是上述对称过程: + +case 2.1: 父节点为红色且其叔叔节点也为红色,则将父亲、叔叔置为黑色,祖父置为红色。 + +![rb2.1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb2.1.png) + +case 2.2 若无叔叔节点或者其叔叔节点为黑色 + +![rb2.2.1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb2.2.1.png) + +情况2.2.1:x的叔叔节点y是黑色且x是一个左孩子 + +![rb2.2.2.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb2.2.2.png) + + + + +```cpp +_Rb_tree_node_base *const __y = __xpp->_M_left; // 保存叔叔节点 +if (__y && __y->_M_color == _S_red) { // 叔叔节点存在且为红色 + __x->_M_parent->_M_color = _S_black; // 父亲节点改为黑色 + __y->_M_color = _S_black; // 祖父节点改为红色 + __xpp->_M_color = _S_red; + __x = __xpp; +} else { // 若无叔叔节点或者其叔叔节点为黑色 + if (__x == __x->_M_parent->_M_left) { // 当前节点为父亲节点的左孩子 + __x = __x->_M_parent; + local_Rb_tree_rotate_right(__x, __root); // 以父节点右旋转 + } + __x->_M_parent->_M_color = _S_black; // 父节点置为黑色 + __xpp->_M_color = _S_red; // 祖父节点置为红色 + local_Rb_tree_rotate_left(__xpp, __root); // 左旋转 +} + +``` + + + +`_Rb_tree_insert_and_rebalance`完整解析: + +```cpp +void +_Rb_tree_insert_and_rebalance(const bool __insert_left, + _Rb_tree_node_base *__x, + _Rb_tree_node_base *__p, + _Rb_tree_node_base &__header) throw() { + _Rb_tree_node_base * &__root = __header._M_parent; + + // Initialize fields in new node to insert. + __x->_M_parent = __p; + __x->_M_left = 0; + __x->_M_right = 0; + __x->_M_color = _S_red; + + // 处理__header部分 + // Insert. + // Make new node child of parent and maintain root, leftmost and + // rightmost nodes. + // N.B. First node is always inserted left. + if (__insert_left) { + __p->_M_left = __x; // also makes leftmost = __x when __p == &__header + + if (__p == &__header) { + __header._M_parent = __x; + __header._M_right = __x; + } else if (__p == __header._M_left) + __header._M_left = __x; // maintain leftmost pointing to min node + } else { + __p->_M_right = __x; + + if (__p == __header._M_right) + __header._M_right = __x; // maintain rightmost pointing to max node + } + + // Rebalance. + while (__x != __root + && __x->_M_parent->_M_color == _S_red) // 若新插入节点不是为RB-Tree的根节点,且其父节点color属性也是红色,即违反了性质4. + { + _Rb_tree_node_base *const __xpp = __x->_M_parent->_M_parent; // 祖父节点 + + if (__x->_M_parent == __xpp->_M_left) // 父亲是祖父节点的左孩子 + { + _Rb_tree_node_base *const __y = __xpp->_M_right; // 得到叔叔节点 + if (__y && __y->_M_color == _S_red) // case1: 叔叔节点存在,且为红色 + { + /** + * 解决办法是:颜色翻转,父亲与叔叔的颜色都变为黑色,祖父节点变为红色,然后当前节点设为祖父,依次网上来判断是否破坏了红黑树性质 + */ + __x->_M_parent->_M_color = _S_black; // 将其父节点改为黑色 + __y->_M_color = _S_black; // 将其叔叔节点改为黑色 + __xpp->_M_color = _S_red; // 将其祖父节点改为红色 + __x = __xpp; // 修改_x,往上回溯 + } else { // 无叔叔或者叔叔为黑色 + if (__x == __x->_M_parent->_M_right) { // 当前节点为父亲节点的右孩子 + __x = __x->_M_parent; + local_Rb_tree_rotate_left(__x, __root); // 以父节点进行左旋转 + } + // 旋转之后,节点x变成其父节点的左孩子 + __x->_M_parent->_M_color = _S_black; // 将其父亲节点改为黑色 + __xpp->_M_color = _S_red; // 将其祖父节点改为红色 + local_Rb_tree_rotate_right(__xpp, __root); // 以祖父节点右旋转 + } + } else { // 父亲是祖父节点的右孩子 + _Rb_tree_node_base *const __y = __xpp->_M_left; // 保存叔叔节点 + if (__y && __y->_M_color == _S_red) { // 叔叔节点存在且为红色 + __x->_M_parent->_M_color = _S_black; // 父亲节点改为黑色 + __y->_M_color = _S_black; // 祖父节点改为红色 + __xpp->_M_color = _S_red; + __x = __xpp; + } else { // 若无叔叔节点或者其叔叔节点为黑色 + if (__x == __x->_M_parent->_M_left) { // 当前节点为父亲节点的左孩子 + __x = __x->_M_parent; + local_Rb_tree_rotate_right(__x, __root); // 以父节点右旋转 + } + __x->_M_parent->_M_color = _S_black; // 父节点置为黑色 + __xpp->_M_color = _S_red; // 祖父节点置为红色 + local_Rb_tree_rotate_left(__xpp, __root); // 左旋转 + } + } + } + //若新插入节点为根节点,则违反性质2 + //只需将其重新赋值为黑色即可 + __root->_M_color = _S_black; +} +``` + +## 5.2插入总结 + +根据上述插入过程与源码分析,我们得出下面三种: +假设P代码父亲节点,N代表当前新插入节点,U代表叔叔节点,G代表祖父节点。 + + +case 1:U为红色,P、N也都为红色,则可以通过改变颜色,自底向上递归调整,下次N就变味G,往上判断即可。如果碰巧将根节点染成了红色, 可以在算法的最后强制root改为黑。 + +![1_1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/1_1.png) + +case 2:U为黑色,考虑N是P的左孩子还是右孩子。 + +case2.1 如果是右孩子,先进行左旋转,再进入下一种情况。 + + +![2_1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/2_1.png) + +case2.2 可能是上述情况变化而来,但不一定是!策略为:右旋转,改变颜色。 + +![rb2_2.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb2_2.png) + + +经过上述源码的分析得知,红黑树插入为镜像变换,另一种情况刚好相反。 + + +删除操作,比较复杂,这里就暂时没分析了,后面补上。。。 + + + +## 6.使用 + +前面说了那么多,如何使用呢? + +引入头文件: +``` +#include或者 +``` +类定义: +``` +_Rb_tree, less> itree; +``` +然后调用相应函数即可。 diff --git a/stl_src/set_multiset.md b/stl_src/set_multiset.md new file mode 100644 index 0000000..e047813 --- /dev/null +++ b/stl_src/set_multiset.md @@ -0,0 +1,214 @@ + +# STL之set与multiset那些事 +set/multiset以rb_tree为底层结构,因此有元素自动排序特性。排序的依据是key,而set/multiset元素的value和key合二为一:value就是key。 + + +我们无法使用set/multiset的iterators改变元素值(因为key有其严谨排列规则)。 +set/multiset的iterator是其底部RB tree的const-iterator,就是为了禁止用户对元素赋值。 + +set元素的key必须独一无二,因此其insert使用的是rb_tree的`_M_insert_unique()`,而multiset元素的key可以重复,因此其insert使用的是rb_tree的`_M_insert_equal()`。 + + +## 1.set + +针对set源码比较简单,故从下面几个问题出发。 + + +> 第一个问题:key是value,value也是key。 + + +具体代码再第二个问题中会有,这里给出我们通常写代码后内部逻辑,我们看到里面有个红黑树,而红黑树的定义key与value是一样的,所以回答了这个问题。(源码中typedef都是来自key)。 + +```cpp +template, + typename _Alloc = std::allocator<_Key> > +class set +{ + // concept requirements + typedef typename _Alloc::value_type _Alloc_value_type; + +public: + // typedefs: + //@{ + /// Public typedefs. + typedef _Key key_type; + typedef _Key value_type; // value也是key + typedef _Compare key_compare; + typedef _Compare value_compare; + typedef _Alloc allocator_type; + //@} + +private: + + typedef _Rb_tree, + key_compare, _Key_alloc_type> _Rep_type; + _Rep_type _M_t; // Red-black tree representing set. +}; +``` + +![set_key.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/set_key.png) + +> 第二个问题:无法使用迭代器改变元素值。 + +无法使用迭代器改变元素值我们看后面迭代器,发现全部用的是`const_iterator`,所以第二个问题也回答完毕。 + +```cpp +template, + typename _Alloc = std::allocator<_Key> > +class set +{ +private: + + typedef _Rb_tree, + key_compare, _Key_alloc_type> _Rep_type; + _Rep_type _M_t; // Red-black tree representing set. + +public: + typedef typename _Rep_type::const_iterator iterator; + typedef typename _Rep_type::const_iterator const_iterator; + typedef typename _Rep_type::const_reverse_iterator reverse_iterator; + typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator; +}; +``` + +经过前面分析,让我们想起了queue、priority_queue、stack,他们都使用的是底层的容器,所以称为容器适配器,而set也是使用底层的容器,所以也可以被称为container adapter,即容器适配器。 + +> 第三个问题:插入是唯一的key。 + +底部调用的是`_M_insert_unique`。 +```cpp +template +set(_InputIterator __first, _InputIterator __last) +: _M_t() +{ _M_t._M_insert_unique(__first, __last); } +``` + +我们来简单看一下这个函数实现: +下面`_M_get_insert_unique_pos`返回的是个pair,如果插入的key相同则pair的second为0,根据是否为0可以判断是否key重复,在下面代码中判断时候,当second不为0,也就是不重复的时候,那么就可以直接插入,此时直接构造一个second为true的pair,否则构造一个second为false的pair。 + +```cpp +template +#if __cplusplus >= 201103L +template +#endif +pair::iterator, bool> +_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: +_M_insert_unique( _Arg && __v ) + +{ + typedef pair _Res; + pair<_Base_ptr, _Base_ptr> __res + = _M_get_insert_unique_pos( _KeyOfValue() ( __v ) ); + + if ( __res.second ) + return(_Res( _M_insert_( __res.first, __res.second, + _GLIBCXX_FORWARD( _Arg, __v ) ), + true ) ); + + return(_Res( iterator( static_cast<_Link_type>(__res.first) ), false ) ); +} + +``` + +我们再看看上面提到的函数: +```cpp +template +pair::_Base_ptr,typename _Rb_tree<_Key, _Val, _KeyOfValue,Compare, _Alloc>::_Base_ptr> +_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: +_M_get_insert_unique_pos(const key_type& __k) +{ + // typedef pair + typedef pair<_Base_ptr, _Base_ptr> _Res; + // _x表示当前节点,_y表示_x的父节点 + _Link_type __x = _M_begin(); + _Link_type __y = _M_end(); + bool __comp = true; + + // 寻找插入点 + while (__x != 0) + { + __y = __x; + // __k<__x是否为true + __comp = _M_impl._M_key_compare(__k, _S_key(__x)); + // __k<__x就往左孩子查找,否则右孩子查找 + __x = __comp ? _S_left(__x) : _S_right(__x); + } + iterator __j = iterator(__y); + // __k<__y,往__y的左孩子插入节点即可,不是做插入,是找到位置即可。 + if (__comp) + { + // 特殊位置 + if (__j == begin()) + return _Res(__x, __y); + else + --__j; // 左孩子 这里调用了--操作符 + } + // __j<__k,返回当前节(__x=0)点与父节点 + if (_M_impl._M_key_compare(_S_key(__j._M_node), __k)) + return _Res(__x, __y); + // _j>=__k,插入失败 + return _Res(__j._M_node, 0); +} +``` + + +上述pair的使用给了我一个启发,竟然可以这样用,那么我们来学习一下: +```cpp +cout<<"flag: "< _Res; // 也来用一下typedef后的pair +cout<<_Res(1,true).first< +multiset(_InputIterator __first, _InputIterator __last) +: _M_t() +{ _M_t._M_insert_equal(__first, __last); } +``` +下面来分析一下`_M_insert_equal`: + +```cpp +typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator +_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: +_M_insert_equal(_Arg&& __v) +{ + pair<_Base_ptr, _Base_ptr> __res = _M_get_insert_equal_pos(_KeyOfValue()(__v)); + return _M_insert_(__res.first, __res.second, _GLIBCXX_FORWARD(_Arg, __v)); +} +``` + +我们继续追踪上述的`_M_get_insert_equal_pos`函数: + +```cpp +template +pair::_Base_ptr, +typename _Rb_tree<_Key, _Val, _KeyOfValue, +_Compare, _Alloc>::_Base_ptr> +_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: +_M_get_insert_equal_pos(const key_type& __k) +{ + typedef pair<_Base_ptr, _Base_ptr> _Res; + _Link_type __x = _M_begin(); + _Link_type __y = _M_end(); + while (__x != 0) + { + __y = __x; + __x = _M_impl._M_key_compare(__k, _S_key(__x)) ? + _S_left(__x) : _S_right(__x); + } + return _Res(__x, __y); +} +``` +我们对比multiset与set的这几个函数发现,返回的pair有着显著的差异,之前的set需要返回最终是否插入成功,因为key不可重复,而multiset不需要返回是否插入成功,所以pair中不存在bool类型,故它是直接返回的插入点所构成的pair,因此,与之前相比较而言,不管你有多少个key,重复如何,都可以插入进去。 + diff --git a/stl_src/traits.md b/stl_src/traits.md new file mode 100644 index 0000000..2ce4d47 --- /dev/null +++ b/stl_src/traits.md @@ -0,0 +1,256 @@ +# C++ STL 源码剖析之 Traits 编程技法 + +## 0.导语 + +在 STL 编程中,容器和算法是独立设计的,即数据结构和算法是独立设计的,连接容器和算法的桥梁就是迭代器了,迭代器使其独立设计成为可能。如下图所示: + +![](https://raw.githubusercontent.com/Light-City/cloudimg/master/rela.png) + +上图给出了 STL 的目标就是要把数据和算法分开,分别对其进行设计,之后通过一种名为 iterator 的东西,把这二者再粘接到一起。 + +设计模式中,关于 iterator 的描述为:**一种能够顺序访问容器中每个元素的方法,使用该方法不能暴露容器内部的表达方式。而类型萃取技术就是为了要解决和 iterator 有关的问题的。** + +它将范型算法(find, count, find_if)用于某个容器中,最重要的是要给算法提供一个访问容器元素的工具,iterator 就扮演着这个重要的角色。 + +而在算法中我们可能会定义简单的中间变量或者设定算法的返回变量类型,这时候需要知道迭代器所指元素的类型是什么,但是由于没有 typeof 这类判断类型的函数,我们无法直接获取,那该如何是好?本文就来具体阐述。 + +对于迭代器来说就是一种智能指针,因此,它也就拥有了一般指针的所有特点——能够对其进行\*和->操作。但是在遍历容器的时候,不可避免的要对遍历的容器内部有所了解,所以,干脆把迭代器的开发工作交给容器的设计者好了,如此以来,所有实现细节反而得以封装起来不被使用者看到,这正是为什么每一种 STL 容器都提供有专属迭代器的缘故。 + +而 Traits 在`bits/stl_iterator_base_types.h`中: + +```c++ +template +struct iterator_traits<_Tp*> +{ + typedef ptrdiff_t difference_type; + typedef typename _Tp::value_type value_type; + typedef typename _Tp::pointer pointer; + typedef typename _Tp::reference reference; + typedef typename _Tp::iterator_category iterator_category; +}; +``` + +看的一脸懵逼吧,没事,看完本节,入门 STL,哈哈~ + +## 1.template 参数推导 + +首先,在算法中运用迭代器时,很可能会用到**其相应型别(associated type)**(迭代器所指之物的型别)。假设算法中有必要声明一个变量,以"迭代器所指对象的型别"为型别,该**怎么办呢?** + +**解决方法是:利用 function template 的参数推导机制。** + +例如: + +如果 T 是某个指向特定对象的指针,那么在 func 中需要指针所指向对象的型别的时候,怎么办呢?这个还比较容易,模板的参数推导机制可以完成任务, + +```c++ +template +inline +void func(I iter) { + func_impl(iter, *iter); // 传入iter和iter所指的值,class自动推导 +} +``` + +通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型。 + +```c++ +template +void func_impl(I iter, T t) { + T tmp; // 这里就是迭代器所指物的类别 + // ... 功能实现 +} + +int main() { + int i; + func(&i); +} +``` + +但是,**函数的"template 参数推导机制"推导的只是参数,无法推导函数的返回值类型。万一需要推导函数的传回值,就无能为力了**。因此,我们引出下面的方法。 + +## 2.声明内嵌型别 + +**迭代器所指对象的型别,称之为迭代器的 value type。** + +尽管在 func_impl 中我们可以把 T 作为函数的返回值,但是问题是用户需要调用的是 func。 + +```c++ +template +T func_impl(I iter, T t) { + T tmp; // 这里就是迭代器所指物的类别 + // ... 功能实现 +} +template +(*T) func(T t) { // !!!Wrong code + return func_impl(t, *t); // forward the task to func_impl +} +int main() { + int i =10; + cout< +struct MyIter { + typedef T value_type; // 内嵌型别声明 + T* ptr; + MyIter(T* p = 0) : ptr(p) {} + T& operator*() const { return *ptr; } +}; + +template +typename I::value_type +func(I ite) { + std::cout << "class version" << std::endl; + return *ite; +} +int main() { + // ... + MyIter ite(new int(8)); + cout << func(ite); // 输出8 +} +``` + +很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为 func 如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过: + +```c++ +int *p = new int(5); +cout< +struct MyIter { + typedef T value_type; // 内嵌型别声明 + T* ptr; + MyIter(T* p = 0) : ptr(p) {} + T& operator*() const { return *ptr; } +}; + +template +typename I::value_type +func(I ite) { + std::cout << "class version" << std::endl; + return *ite; +} +template +I +func(I* ite) { + std::cout << "pointer version" << std::endl; + return *ite; +} +template +I func(const I* ite) { + std::cout << "const pointer version" << std::endl; + return *ite; +} +int main() { + // ... + MyIter ite(new int(8)); + cout << func(ite)< + +template +struct MyIter { + typedef T value_type; // 内嵌型别声明 + T* ptr; + MyIter(T* p = 0) : ptr(p) {} + T& operator*() const { return *ptr; } +}; +// class type +template +struct iterator_traits { + typedef typename T::value_type value_type; +}; +// 偏特化1 +template +struct iterator_traits { + typedef T value_type; +}; +// 偏特化2 +template +struct iterator_traits { + typedef T value_type; +}; + +template +typename iterator_traits::value_type +// 首先询问iterator_traits::value_type,如果传递的I为指针,则进入特化版本,iterator_traits直接回答;如果传递进来的I为class type,就去询问T::value_type. +func(I ite) { + std::cout << "normal version" << std::endl; + return *ite; +} +int main() { + // ... + MyIter ite(new int(8)); + std::cout << func(ite)<::value_type`,如果传递的 I 为指针,则进入特化版本,`iterator_traits`直接回答`T`;如果传递进来的`I`为`class type`,就去询问`T::value_type`. + +上述的通俗解释为算法(func)问 iterator_traits(我),但是 iterator_traits(我)发现手上是指针的时候,就由我来替它回答。如果是 class type,iterator_traits(我)就继续问(他---T::value_type)。 + +**总结:通过定义内嵌类型,我们获得了知晓 iterator 所指元素类型的方法,通过 traits 技法,我们将函数模板对于原生指针和自定义 iterator 的定义都统一起来,我们使用 traits 技法主要是为了解决原生指针和自定义 iterator 之间的不同所造成的代码冗余,这就是 traits 技法的妙处所在。** + +学习书籍: + +> 侯捷《 STL 源码剖析》 + +学习文章: + +> https://juejin.im/post/5b1a43fb51882513bf1795c6 + +> https://www.cnblogs.com/mangoyuan/p/6446046.html + +> http://www.cppblog.com/nacci/archive/2005/11/03/911.aspx \ No newline at end of file diff --git a/stl_src/typename.md b/stl_src/typename.md new file mode 100644 index 0000000..df66079 --- /dev/null +++ b/stl_src/typename.md @@ -0,0 +1,137 @@ +# typename + +STL底层源码有下面几行,typedef与typename联用,这几个看着好复杂,究竟啥意思,我们今天一起来剖析! + +```c++ +template +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; +}; +``` + +## typename的常见用法 + +首先学习一下typename的常见用法: + +```c++ +template +int compare(const T &a, const T &b) +{ + return a>b?a:b; +} +``` + +上述只是个案例程序,如果想写的比较完整比较大小,还得考虑特化版本,也许你会想到上面这段代码中的`typename`换成`class`也一样可以,不错!那么这里便有了疑问,这两种方式有区别么?查看C++ Primer之后,发现两者完全一样. + +### 类作用域 + +在类外部访问类中的名称时,可以使用类作用域操作符,形如`MyClass::name`的调用通常存在三种:**静态数据成员、静态成员函数和嵌套类型**: + +```c++ +struct MyClass { + static int A; //静态成员 + static int B(){cout<<"B()"< +void foo() { + T::iterator * iter; + // ... +} +``` + +这段代码的目的是什么?多数人第一反应可能是:作者想定义一个指针`iter`,它指向的类型是包含在类作用域`T`中的`iterator`。可能存在这样一个包含`iterator`类型的结构: + +```c++ +struct MyIterator { + struct iterator { + + }; +}; +``` + +调用如下: + +```c++ +foo(); +``` + +这样一来,`iter`那行代码就很明显了,它是一个`MyIterator::iterator`类型的指针。我们猜测是这样的,现实是不是呢? + +可是,如果是像`T::iterator`这样呢?`T`是模板中的类型参数,它只有等到模板实例化时才会知道是哪种类型,更不用说内部的`iterator`。通过前面类作用域的介绍,我们可以知道,`T::iterator`实际上可以是以下三种中的任何一种类型: + +- 静态数据成员 +- 静态成员函数 +- 嵌套类型 + +前面例子中的`ContainsAType::iterator`是嵌套类型,完全没有问题。可如果是静态数据成员呢?如果实例化`foo`模板函数的类型是像这样的: + +```c++ +struct MyIterator { + static int iterator; +}; +``` + +那么,`T::iterator * iter;`被编译器实例化为`MyIterator::iterator * iter;`,这是什么?前面是一个静态成员变量而不是类型,那么这便成了一个乘法表达式,只不过`iter`在这里没有定义,编译器会报错: + +```c++ +error: no type named ‘iterator’ in ‘struct MyIterator’ +``` + +### typename + +对于用于模板定义的依赖于模板参数的名称,只有在实例化的参数中存在这个类型名,或者这个名称前使用了`typename`关键字来修饰,编译器才会将该名称当成是类型。除了以上这两种情况,绝不会被当成是类型。 + +因此,如果你想直接告诉编译器`T::iterator`是类型而不是变量,只需用`typename`修饰: + +```c++ +template +void foo() { + typename T::iterator * iter; +} +``` + +这样编译器就可以确定`T::iterator`是一个类型,而不再需要等到实例化时期才能确定,因此消除了前面提到的歧义。 + +### 剖析源码 + +回到STL源码 + +```c++ +template +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; +}; +``` + +看到上面的,我们就一下子清楚了,无非就是使用typename告诉编译器`_Iterator::iterator_category`是一个类型,然后使用typedef重命名一下,其余类似! + diff --git a/stl_src/unordered_map.md b/stl_src/unordered_map.md new file mode 100644 index 0000000..1f9ded2 --- /dev/null +++ b/stl_src/unordered_map.md @@ -0,0 +1,356 @@ +# C++ STL源码剖析之unordered_map、unordered_multimap、unordered_set、unordered_multiset + +## 0.导语 + +前面学到了hashtable,而这节是hashtable的容器适配器:unordered_map。 + +所以无序map的底层容器采用hashtable。 + +unordered_map与unordered_multimap的源码在`unordered_map.h`这个文件中。 + + +## 1.undered_map与unordered_multimap本质区别 +先来看一下undered_map源码: + +```cpp +template, +class _Pred = std::equal_to<_Key>, +class _Alloc = std::allocator > > +class unordered_map +{ + typedef __umap_hashtable<_Key, _Tp, _Hash, _Pred, _Alloc> _Hashtable; + _Hashtable _M_h; +}; +``` + +去看底层容器的`__umap_hashtable`的声明: + +```cpp +template +using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>; + +template, + typename _Pred = std::equal_to<_Key>, + typename _Alloc = std::allocator >, + typename _Tr = __umap_traits<__cache_default<_Key, _Hash>::value>> +using __umap_hashtable = _Hashtable<_Key, std::pair, +_Alloc, __detail::_Select1st, +_Pred, _Hash, +__detail::_Mod_range_hashing, +__detail::_Default_ranged_hash, +__detail::_Prime_rehash_policy, _Tr>; +``` +可以得到下面结论: +hashtable的模板参数: +```cpp +template +``` +默认情况下,undered_map采用: +- H1为hash +- H2为_Mod_range_hashing +- _Hash为_Default_ranged_hash +- _RehashPolicy为_Prime_rehash_policy +- _Traits为_Tr +对于最后的_Tr,非常重要,因为正是因为这个参数,才有undered_multimap。 +具体分析看下面: + + +_Tr如下: + +```cpp +typename _Tr = __umap_traits<__cache_default<_Key, _Hash>::value>> +``` +_Tr使用了`__umap_traits`,我们继续往下看: +```cpp + +template +using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>; +``` +可以对比上述两个发现__umap_traits里面有一串__cache_default,我们再看一下模板参数为bool类型,故可以打印出来是false还是true,经过实测,为false,表示不缓存hash code。 + +```cpp +template +using __cache_default += __not_<__and_<__is_fast_hash<_Hash>, +__detail::__is_noexcept_hash<_Tp, _Hash>>>; +``` +继续看`__umap_traits`,这个实际上是调用`_Hashtable_traits`,我们继续往下: + +```cpp +template +struct _Hashtable_traits +{ + using __hash_cached = __bool_constant<_Cache_hash_code>; + using __constant_iterators = __bool_constant<_Constant_iterators>; + using __unique_keys = __bool_constant<_Unique_keys>; +}; +``` +看到有三个using,理解为三个typedef,依次表示:hash code缓存与否,是否是常迭代器,是否是唯一的key,再往上回头看,传递进来的是三个模板参数,分别是false,false,true,也验证了undered_map是唯一的key,那么对应的undered_multimap就是不唯一的key,最后一个参数为false。我们翻阅到相应代码如下: + +```cpp +/// Base types for unordered_multimap. +template +using __ummap_traits = __detail::_Hashtable_traits<_Cache, false, false>; +``` + +小结,在上面分析,我们知道了unordered_map与unordered_multimap的本质区别,也发现了如何在底层源码上用一个容器实现两个容器适配器! + +## 2.undered_set与unordered_multiset本质区别 +分析同前面一样,先看undered_set: + +```cpp +template, + class _Pred = std::equal_to<_Value>, + class _Alloc = std::allocator<_Value> > +class unordered_set +{ + typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc> _Hashtable; + _Hashtable _M_h; +} + +template +using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>; + +template, + typename _Pred = std::equal_to<_Value>, + typename _Alloc = std::allocator<_Value>, + typename _Tr = __uset_traits<__cache_default<_Value, _Hash>::value>> +using __uset_hashtable = _Hashtable<_Value, _Value, _Alloc, + __detail::_Identity, _Pred, _Hash, + __detail::_Mod_range_hashing, + __detail::_Default_ranged_hash, + __detail::_Prime_rehash_policy, _Tr>; +``` +可以看到传递给`_Hashtable_traits`的是false,true,true。对于undered_set来说使用的是const iterator与唯一的key,我们再看一下unordered_multiset: + +```cpp +template +using __umset_traits = __detail::_Hashtable_traits<_Cache, true, false>; +``` +再将两者对比一下,本质就是undered_set不允许key重复,而undered_multiset允许key重复。 + +## 3.三大结论 + +现在,我们有了前面基础,依次列出前面四个容器适配器: + +(1) undered_map +```cpp +template +using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>; +``` +(2) undered_multimap +```cpp +template +using __umap_traits = __detail::_Hashtable_traits<_Cache, false, false>; +``` +(3) undered_set + +```cpp +template +using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>; +``` +(4) undered_set + +```cpp +template +using __uset_traits = __detail::_Hashtable_traits<_Cache, true, false>; +``` + +对比后,得出 + +- 结论1:undered_map与undered_set不允许key重复,而带multi的则允许key重复; +- 结论2:undered_map与undered_multimap采用的迭代器是iterator,而undered_set与undered_multiset采用的迭代器是const_iterator。 +- 结论3:undered_map与undered_multimap的key是key,value是key+value;而undered_set与undered_multiset的key是Value,Value也是Key。 + +最后一个结论对比看下面(我们看传递给hashtable的第一与第二个参数): + +undered_map与undered_multimap: +```cpp +using __umap_hashtable = _Hashtable<_Key, +std::pair, +_Alloc, __detail::_Select1st, +_Pred, _Hash, +__detail::_Mod_range_hashing, +__detail::_Default_ranged_hash, +__detail::_Prime_rehash_policy, _Tr>; +``` +undered_set与undered_multiset: +```cpp +template, +typename _Pred = std::equal_to<_Value>, +typename _Alloc = std::allocator<_Value>, +typename _Tr = __uset_traits<__cache_default<_Value, _Hash>::value>> +using __uset_hashtable = _Hashtable<_Value, _Value, _Alloc, +__detail::_Identity, _Pred, _Hash, +__detail::_Mod_range_hashing, +__detail::_Default_ranged_hash, +__detail::_Prime_rehash_policy, _Tr>; +``` + +## 4.undered_map重要函数 + +> 初始化 + +可以在下面的构造函数中看到undered_map的默认桶数为10。 + +在undered_map的底层默认采用hasher(),也就是H1,也就是std::hash + +```cpp +unordered_map(size_type __n = 10, + const hasher& __hf = hasher(), + const key_equal& __eql = key_equal(), + const allocator_type& __a = allocator_type()) +: _M_h(__n, __hf, __eql, __a) +{ } +``` +下面测试是否采用默认的hash: + +```cpp +unordered_map um; +hash h; +cout< 是否空、大小、最大大小 + +```cpp +bool +empty() const noexcept +{ return _M_h.empty(); } +/// Returns the size of the %unordered_map. +size_type +size() const noexcept +{ return _M_h.size(); } + +/// Returns the maximum size of the %unordered_map. +size_type +max_size() const noexcept +{ return _M_h.max_size(); } +``` + +> begin与end + +```cpp +iterator +begin() noexcept +{ return _M_h.begin(); } +iterator +end() noexcept +{ return _M_h.end(); } +``` +> insert +五种插入方式 + +```cpp +// value +std::pair +insert(const value_type& __x) +{ return _M_h.insert(__x); } + +// pair +std::pair +insert(_Pair&& __x) +{ return _M_h.insert(std::forward<_Pair>(__x)); } + +// iterator+value +iterator +insert(const_iterator __hint, const value_type& __x) +{ return _M_h.insert(__hint, __x); } + + +// first到last范围插入 +template +void +insert(_InputIterator __first, _InputIterator __last) +{ _M_h.insert(__first, __last); } + +// 初始化列表插入 + +void +insert(initializer_list __l) +{ _M_h.insert(__l); } +``` + +> 删除 + +三种删除方式 + +```cpp +// iterator +iterator +erase(iterator __position) +{ return _M_h.erase(__position); } + +// key +size_type +erase(const key_type& __x) +{ return _M_h.erase(__x); } + +// first到last范围 + +iterator +erase(const_iterator __first, const_iterator __last) +{ return _M_h.erase(__first, __last); +``` + +> 清除 + +```cpp +void +clear() noexcept +{ _M_h.clear(); } +``` +> hash_function + +得到该undered_map的hash_function +```cpp +hasher +hash_function() const +{ return _M_h.hash_function(); } +``` +使用: +```cpp +unordered_map um; +cout< key_eq +key_eq返回的是std::equal_to +使用如下: +```cpp +unordered_map um; +cout< 查找与获取value + +```cpp +iterator +find(const key_type& __x) +{ return _M_h.find(__x); } +mapped_type& +operator[](const key_type& __k) +{ return _M_h[__k]; } +mapped_type& +at(const key_type& __k) +{ return _M_h.at(__k); } +``` +除了这些函数还有获取桶,最大桶数、加载因子、rehash等等,就是没有排序,因为hashtable没有提供排序功能。hashtable在查找、删除和插入节点是常数时间,优于RB-Tree红黑树。 + +同理,unordered_set、unordered_multiset、unordered_multimap与undered_map一样的函数,所以就不阐述了。 \ No newline at end of file diff --git a/stl_src/vector.md b/stl_src/vector.md new file mode 100644 index 0000000..3a0a7d5 --- /dev/null +++ b/stl_src/vector.md @@ -0,0 +1,785 @@ +# STL源码剖析之vector + +## 0.导语 + +vector的数据安排以及操作方式,与array非常相似。两者的唯一差别在于空间的运用的灵活性,array是静态的,一旦配置了就不能改变,而 vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。下面一起来看一下vector的"内部机制",怎么来实现空间配置策略的。 + +## 1.vector + +在`_Vector_base`中开头有两行比较难理解,下面一个一个分析: + +### 1.1 _Tp_alloc_type +开头处定义: +``` + typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type; +``` + +在`__gnu_cxx::__alloc_traits`中:对应文件为:`ext/alloc_traits.h` + +```cpp + template + struct rebind + { typedef typename _Base_type::template rebind_alloc<_Tp> other; }; +``` +等价于 +``` +typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other +``` +等价于: + +```cpp +typename _Base_type::template rebind_alloc<_Tp> +``` + +而`_Base_type`是: + +```cpp +typedef std::allocator_traits<_Alloc> _Base_type; +``` + +所以上述等价于: + +```cpp +typename std::allocator_traits<_Alloc>::template rebind_alloc<_Tp> +``` + +继续到`allocator_traits`中寻找 + +找到了: + +```cpp + template + using rebind_alloc = allocator<_Up>; +``` + +于是: + +```cpp +std::allocator_traits<_Alloc>::template rebind_alloc<_Tp> +``` + +等价于: + +```cpp +allocator<_Tp> +``` + +> 小结 + +```cpp + typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type; +``` + +等价于: + +```cpp +typedef allocator<_Tp> _Tp_alloc_type +``` + +### 1.2 pointer + +而`pointer`: + +```cpp + typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer + pointer; +``` + +等价于: + +```cpp + typedef typename __gnu_cxx::__alloc_traits>::pointer + pointer; +``` + +根据下面两行: + +```cpp +typedef std::allocator_traits<_Alloc> _Base_type; +typedef typename _Base_type::pointer pointer; +``` + +又等价于: + +``` + typedef std::allocator_traits<_Alloc>::pointer + pointer; +``` + +在`allocator_traits`中找到下面: + +```cpp + /** + * @brief The allocator's pointer type. + * + * @c Alloc::pointer if that type exists, otherwise @c value_type* + */ + typedef __pointer pointer; +``` + +注释中说了如果存在就是`Alloc::pointer`,否则为` value_type *`。 + +> 小结 + +```cpp + typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer + pointer; + // 或者 + typedef typename __gnu_cxx::__alloc_traits>::pointer + pointer; +``` + +等价于 + +```cpp +/** +* @brief The allocator's pointer type. +* +* @c Alloc::pointer if that type exists, otherwise @c value_type* +*/ +typedef __pointer pointer; + +``` + +如果存在`_Tp_alloc_type::pointer`也就是`allocator<_Tp>`存在就是`allocator<_Tp>::pointer`, + +这个看`allocator.h`源码: + +```cpp +typedef _Tp* pointer; + +``` + +否则为` value_type*`。而`value_type`为: + +```cpp +typedef typename _Alloc::value_type value_type; + +``` + +所以`value_type*`推导出为: + +```cpp +_Tp::value_type* + +``` + +### 1.3 vector的内存管理 + +`_Vector_base`中有一个**内存管理器**`_Vector_impl`类,该结构体继承`allocator`(根据上述1.1等价条件得出)。 + + +```cpp +template +struct _Vector_base { + //__alloc_traits -> allocator_traits + // typedef allocator<_Tp> _Tp_alloc_type + typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template + rebind<_Tp>::other _Tp_alloc_type; + // _Tp::value_type* or _Tp* + typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer + pointer; + + struct _Vector_impl + : public _Tp_alloc_type { + pointer _M_start; // 使用空间起始位置 + pointer _M_finish; // 使用空间结束位置 + pointer _M_end_of_storage; // 可使用空间结束位置 + + _Vector_impl() + : _Tp_alloc_type(), _M_start(0), _M_finish(0), _M_end_of_storage(0) {} + + _Vector_impl(_Tp_alloc_type const &__a) + + // vector数据交换,只交换内存地址,不交换数据 + void _M_swap_data(_Vector_impl &__x) + + _GLIBCXX_NOEXCEPT + { + std::swap(_M_start, __x._M_start); + std::swap(_M_finish, __x._M_finish); + std::swap(_M_end_of_storage, __x._M_end_of_storage); + } + }; + +public: + typedef _Alloc allocator_type; + // 前面我们知道_Vector_impl继承自allocator分配器,而这个又是_Tp_alloc_type,所以这里返回的就是_M_impl的基类。 + _Tp_alloc_type & + _M_get_Tp_allocator() + + _GLIBCXX_NOEXCEPT + { return *static_cast<_Tp_alloc_type *>(&this->_M_impl); } + + const _Tp_alloc_type & + _M_get_Tp_allocator() const + + _GLIBCXX_NOEXCEPT + { return *static_cast(&this->_M_impl); } + + allocator_type // 获取传递进来的分配器 + get_allocator() const + + _GLIBCXX_NOEXCEPT + { return allocator_type(_M_get_Tp_allocator()); } + + _Vector_base() + : _M_impl() {} + + _Vector_base(const allocator_type &__a) + + _GLIBCXX_NOEXCEPT + : _M_impl(__a) {} + + _Vector_base(size_t __n) + : _M_impl() { _M_create_storage(__n); } + + _Vector_base(size_t __n, const allocator_type &__a) + : _M_impl(__a) { _M_create_storage(__n); } + +#if __cplusplus >= 201103L + _Vector_base(_Tp_alloc_type&& __a) noexcept + : _M_impl(std::move(__a)) { } + + // 移动构造函数,只交换3个指针,不copy数据 + _Vector_base(_Vector_base&& __x) noexcept + : _M_impl(std::move(__x._M_get_Tp_allocator())) + { this->_M_impl._M_swap_data(__x._M_impl); } + + _Vector_base(_Vector_base&& __x, const allocator_type& __a) + : _M_impl(__a) + { + if (__x.get_allocator() == __a) + this->_M_impl._M_swap_data(__x._M_impl); + else + { + size_t __n = __x._M_impl._M_finish - __x._M_impl._M_start; + _M_create_storage(__n); + } + } +#endif + + ~_Vector_base() + + _GLIBCXX_NOEXCEPT + { + _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage + - this->_M_impl._M_start); + } + +public: + _Vector_impl _M_impl; + + pointer _M_allocate(size_t __n) { + typedef __gnu_cxx::__alloc_traits <_Tp_alloc_type> _Tr; + return __n != 0 ? _Tr::allocate(_M_impl, __n) : 0; // 同_M_deallocate,一直往后调用的就是malloc函数 + } + + void _M_deallocate(pointer __p, size_t __n) { + typedef __gnu_cxx::__alloc_traits <_Tp_alloc_type> _Tr; + if (__p) + _Tr::deallocate(_M_impl, __p, __n); // 最后调用allocator_traits的deallocate,而该函数又是根据传递进来的_M_impl进行deallocate,一直往后,最后调用的就是free函数 + } + +private: + void _M_create_storage(size_t __n) { + this->_M_impl._M_start = this->_M_allocate(__n); + this->_M_impl._M_finish = this->_M_impl._M_start; + this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n; + } +}; +``` + +小结:`_Vector_base`专门负责`vector`的内存管理,内部类`_M_impl`通过继承`_Tp_alloc_type`(也就是allocator)得到内存分配释放的功能,_M_allocate和_M_deallocate分别分配和释放vector所用内存,vector只需要负责元素构造和析构。 + +在vector中,默认内存分配器为`std::allocator<_Tp>` +```cpp +template> +class vector : protected _Vector_base<_Tp, _Alloc> { +} +``` +vector代码中使用基类的内存函数及typedef等: +```cpp +template> +class vector : protected _Vector_base<_Tp, _Alloc> { + typedef _Vector_base<_Tp, _Alloc> _Base; + typedef typename _Base::_Tp_alloc_type _Tp_alloc_type; +public: + typedef typename _Base::pointer pointer; +protected: + using _Base::_M_allocate; + using _Base::_M_deallocate; + using _Base::_M_impl; + using _Base::_M_get_Tp_allocator; +} +``` +## 2.vector迭代器 +在vector中使用了两种迭代器,分别是正向`__normal_iterator`与反向迭代器`reverse_iterator`: + +正向: + +```cpp +typedef __gnu_cxx::__normal_iterator iterator; +typedef __gnu_cxx::__normal_iterator const_iterator; +``` +反向: +```cpp +typedef std::reverse_iterator const_reverse_iterator; +typedef std::reverse_iterator reverse_iterator; +``` + +`__normal_iterator`与`reverse_iterator`都定义于stl_iterator.h,封装了vector元素的指针。 + +### 2.1 正向 + +```cpp +template +class __normal_iterator +{ +protected: + _Iterator _M_current; + + typedef iterator_traits<_Iterator> __traits_type; + +public: + typedef _Iterator iterator_type; + + // iterator必须包含的五种typedef + typedef typename __traits_type::iterator_category iterator_category; + typedef typename __traits_type::value_type value_type; + typedef typename __traits_type::difference_type difference_type; + typedef typename __traits_type::reference reference; + typedef typename __traits_type::pointer pointer; + + _GLIBCXX_CONSTEXPR __normal_iterator() _GLIBCXX_NOEXCEPT + : _M_current(_Iterator()) { } + + explicit + __normal_iterator(const _Iterator& __i) _GLIBCXX_NOEXCEPT + : _M_current(__i) { } + + // Allow iterator to const_iterator conversion + template + __normal_iterator(const __normal_iterator<_Iter, + typename __enable_if< + (std::__are_same<_Iter, typename _Container::pointer>::__value), + _Container>::__type>& __i) _GLIBCXX_NOEXCEPT + : _M_current(__i.base()) { } + + // Forward iterator requirements + reference + operator*() const _GLIBCXX_NOEXCEPT + { return *_M_current; } + + pointer + operator->() const _GLIBCXX_NOEXCEPT + { return _M_current; } + + // 前置++ + __normal_iterator& + operator++() _GLIBCXX_NOEXCEPT + { + ++_M_current; + return *this; + } + + // 后置++ + __normal_iterator + operator++(int) _GLIBCXX_NOEXCEPT + { return __normal_iterator(_M_current++); } + + // 前置-- + // Bidirectional iterator requirements + __normal_iterator& + operator--() _GLIBCXX_NOEXCEPT + { + --_M_current; + return *this; + } + + // 后置-- + __normal_iterator + operator--(int) _GLIBCXX_NOEXCEPT + { return __normal_iterator(_M_current--); } + + // 随机访问迭代器都要重载[]操作符 + // Random access iterator requirements + reference + operator[](difference_type __n) const _GLIBCXX_NOEXCEPT + { return _M_current[__n]; } + + // +=操作符 跳跃n个difference_type + __normal_iterator& + operator+=(difference_type __n) _GLIBCXX_NOEXCEPT + { _M_current += __n; return *this; } + + // +操作符 跳跃n个difference_type + __normal_iterator + operator+(difference_type __n) const _GLIBCXX_NOEXCEPT + { return __normal_iterator(_M_current + __n); } + + // -=操作符 后退n个difference_type + __normal_iterator& + operator-=(difference_type __n) _GLIBCXX_NOEXCEPT + { _M_current -= __n; return *this; } + + // -操作符 后退n个difference_type + __normal_iterator + operator-(difference_type __n) const _GLIBCXX_NOEXCEPT + { return __normal_iterator(_M_current - __n); } + + const _Iterator& + base() const _GLIBCXX_NOEXCEPT + { return _M_current; } +}; +``` + +_M_current是指向迭代器位置的指针,这是一个随机访问型指针,operator+和operator-等移动操作可以直接移动到目的地,非随机访问型指针只能一步步移动。 + +### 2.2 反向 + + +vector还会使用reverse_iterator,即逆序迭代器,顾名思义,其移动方向与普通迭代器相反 + +```cpp +template +class reverse_iterator +: public iterator::iterator_category, + typename iterator_traits<_Iterator>::value_type, + typename iterator_traits<_Iterator>::difference_type, + typename iterator_traits<_Iterator>::pointer, + typename iterator_traits<_Iterator>::reference> +{ +protected: + _Iterator current; + + typedef iterator_traits<_Iterator> __traits_type; + +public: + typedef _Iterator iterator_type; + typedef typename __traits_type::difference_type difference_type; + typedef typename __traits_type::pointer pointer; + typedef typename __traits_type::reference reference; + + // 省略不重要的代码 + + + // 该迭代器是从后面end()开始,需要往前一步,才可以获取到有效的迭代器位置 + reference + operator*() const + { + _Iterator __tmp = current; + return *--__tmp; + } + + // 通过调用上述*操作符直接实现 + pointer + operator->() const + { return &(operator*()); } + + + // 前置++操作符完成后退任务 + reverse_iterator& + operator++() + { + --current; + return *this; + } + + // 后置++ + reverse_iterator + operator++(int) + { + reverse_iterator __tmp = *this; + --current; + return __tmp; + } + + // 前置--操作符完成前进任务 + reverse_iterator& + operator--() + { + ++current; + return *this; + } + + // 后置-- + reverse_iterator + operator--(int) + { + reverse_iterator __tmp = *this; + ++current; + return __tmp; + } + + // +操作符 + reverse_iterator + operator+(difference_type __n) const + { return reverse_iterator(current - __n); } + + // +=操作符 + reverse_iterator& + operator+=(difference_type __n) + { + current -= __n; + return *this; + } + + // -操作符 + reverse_iterator + operator-(difference_type __n) const + { return reverse_iterator(current + __n); } + + // -=操作符 + reverse_iterator& + operator-=(difference_type __n) + { + current += __n; + return *this; + } + + // []操作符 + reference + operator[](difference_type __n) const + { return *(*this + __n); } +}; + +``` + +## 3.vector的数据结构 + +vector内存由_M_impl中的M_start,_M_finish,_M_end_of_storage三个指针管理,所有关于地址,容量大小等操作都需要用到这三个指针: + +```cpp +iterator begin() _GLIBCXX_NOEXCEPT + { return iterator(this->_M_impl._M_start); } +iterator end() _GLIBCXX_NOEXCEPT + { return iterator(this->_M_impl._M_finish); } +reverse_iterator rbegin() noexcept + { return reverse_iterator(end()); } +reverse_iterator rend() noexcept + { return reverse_iterator(begin()); } +size_type size() const _GLIBCXX_NOEXCEPT + { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); } +size_type capacity() const _GLIBCXX_NOEXCEPT + { return size_type(this->_M_impl._M_end_of_storage - this->_M_impl._M_start); } +bool empty() const _GLIBCXX_NOEXCEPT + { return begin() == end(); } +``` +![vector_1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/vector_1.png) + +_M_finish和_M_end_of_storage之间的空间没有数据,有时候这是一种浪费,c++11提供了一个很有用的函数shrink_to_fit(),将这段未使用空间释放,主要调用了下面代码, + + + +```cpp +template +bool vector:: +_M_shrink_to_fit() +{ + if (capacity() - size() < int(_S_word_bit)) // 64位系统为64bytes + return false; + __try + { + _M_reallocate(size()); + return true; + } + __catch(...) + { + return false; + } +} +``` + + +```cpp + template +void vector:: +_M_reallocate(size_type __n) +{ + _Bit_type* __q = this->_M_allocate(__n); + this->_M_impl._M_finish = _M_copy_aligned(begin(), end(), + iterator(__q, 0)); + this->_M_deallocate(); + this->_M_impl._M_start = iterator(__q, 0); + this->_M_impl._M_end_of_storage = __q + _S_nword(__n); +} +``` +而`_M_copy_aligned`通过两个std::copy实现: + +第一次swap把`__first`的指针与`__last`的指针之间的数据拷贝到`__result`指针所指向的起始位置。 +第二次swap获得`__last`的指针对应的迭代器。 + +```cpp +iterator +_M_copy_aligned(const_iterator __first, const_iterator __last, + iterator __result) +{ + // _Bit_type * _M_p; _Bit_type为unsigned long类型 + _Bit_type* __q = std::copy(__first._M_p, __last._M_p, __result._M_p); + return std::copy(const_iterator(__last._M_p, 0), __last, + iterator(__q, 0)); +} + +``` +先分配size()大小的内存空间,用于存储`begin()`与`end()`之间的数据,释放原来的vector空间,新的vector只包含size()数量的数据,并修改`_M_start`与`_M_end_of_storage`指向。 + + +## 4.vector构造与析构 + +```cpp +//使用默认内存分配器 +vector() : _Base() { } +//指定内存分配器 +explicit vector(const allocator_type& __a) : _Base(__a) { } +//初始化为n个__value值,如果没指定就使用该类型默认值 +explicit vector(size_type __n, const value_type& __value = value_type(), + const allocator_type& __a = allocator_type()): _Base(__n, __a) +{ _M_fill_initialize(__n, __value); } +//拷贝构造函数 +vector(const vector& __x) + : _Base(__x.size(), + _Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator())) +{ this->_M_impl._M_finish = + std::__uninitialized_copy_a(__x.begin(), __x.end(), + this->_M_impl._M_start, + _M_get_Tp_allocator()); +} +//c++11的移动构造函数,获取__x的M_start,_M_finish,_M_end_of_storage,并不需要数据拷贝 +vector(vector&& ) noexcept + : _Base(std::move(__x)) { } +//从list中拷贝数据,也是c++11才有的 + vector(initializer_list __l, + const allocator_type& __a = allocator_type()) +: _Base(__a) +{ + _M_range_initialize(__l.begin(), __l.end(), random_access_iterator_tag()); +} +//支持vector使用两个迭代器范围内的值初始化,除了stl的迭代器,也可以是数组地址 +template> +vector(_InputIterator __first, _InputIterator __last, + const allocator_type& __a = allocator_type()) +: _Base(__a) +{ _M_initialize_dispatch(__first, __last, __false_type()); } +//只析构所有元素,释放内存由vector_base完成 +~vector() _GLIBCXX_NOEXCEPT +{ std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,_M_get_Tp_allocator()); } +``` + +## 5.vector + +插入涉及到内存分配,动态调整,与一开始提到的vector与array区别,就在下面体现出: + +```cpp +typename vector<_Tp, _Alloc>::iterator +vector<_Tp, _Alloc>::insert(iterator __position, const value_type& __x) +{ + const size_type __n = __position – begin(); + //插入到最后一个位置,相当于push_back + if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage + && __position == end()) + { + _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x); + ++this->_M_impl._M_finish; + } + else + { + _M_insert_aux(__position, __x); + } + return iterator(this->_M_impl._M_start + __n); +} + +``` +其中`_M_insert_aux`实现: +```cpp +template +void vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x) +{ + //内存空间足够 + if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) + { + _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, + _GLIBCXX_MOVE(*(this->_M_impl._M_finish + - 1))); + ++this->_M_impl._M_finish; + //__position后的元素依次向后移动一个位置 + _GLIBCXX_MOVE_BACKWARD3(__position.base(), + this->_M_impl._M_finish - 2, + this->_M_impl._M_finish – 1); + //目标地址赋值 + *__position = _Tp(std::forward<_Args>(__args)...); + } + else + { + //内存加倍 + const size_type __len = + _M_check_len(size_type(1), "vector::_M_insert_aux"); + const size_type __elems_before = __position - begin(); + pointer __new_start(this->_M_allocate(__len)); + pointer __new_finish(__new_start); + __try + { + //给position位置赋值 + _Alloc_traits::construct(this->_M_impl, + __new_start + __elems_before, + std::forward<_Args>(__args)...); + __x); + __new_finish = 0; + //拷贝position位置前元素 + __new_finish = std::__uninitialized_move_if_noexcept_a + (this->_M_impl._M_start, __position.base(), + __new_start, _M_get_Tp_allocator()); + + ++__new_finish; + //拷贝position位置后元素 + __new_finish + = std::__uninitialized_move_if_noexcept_a + (__position.base(), this->_M_impl._M_finish, + __new_finish, _M_get_Tp_allocator()); + } + __catch(...) + { + if (!__new_finish) + _Alloc_traits::destroy(this->_M_impl, + __new_start + __elems_before); + else + std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator()); + _M_deallocate(__new_start, __len); + __throw_exception_again; + } + + //析构原vector所有元素 + std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish, + _M_get_Tp_allocator()); + //释放原vector内存空间 + _M_deallocate(this->_M_impl._M_start, + this->_M_impl._M_end_of_storage,this->_M_impl._M_start); + //vector内存地址指向新空间 + this->_M_impl._M_start = __new_start; + this->_M_impl._M_finish = __new_finish; + this->_M_impl._M_end_of_storage = __new_start + __len; + } +} +``` +其中`_M_check_len`: + +```cpp + +size_type +_M_check_len(size_type __n, const char* __s) const +{ + if (max_size() - size() < __n) + __throw_length_error(__N(__s)); + + const size_type __len = size() + std::max(size(), __n); //如果n小于当前size,内存加倍,否则内存增长n。 + return (__len < size() || __len > max_size()) ? max_size() : __len; +} +``` +内存分配策略并不是简单的加倍,如果n小于当前size,内存加倍,否则内存增长n。 + + +学习资料: +> 侯捷《STL源码剖析》 + +> https://www.cnblogs.com/coderkian/p/3888429.html \ No newline at end of file diff --git a/stl_src/谈谈STL设计之EBO优化.md b/stl_src/谈谈STL设计之EBO优化.md new file mode 100644 index 0000000..7bf479b --- /dev/null +++ b/stl_src/谈谈STL设计之EBO优化.md @@ -0,0 +1,280 @@ +# STL设计之EBO(空基类优化) + +## 0.导语 + +EBO简称Empty Base Optimization。 + +本节从空类开始,到STL内部,到测试,再到我们自己实现一个EBO,对比性能,最后再测试,总结。 + +## 1.空类 + +定义一个空类:没有成员变量,没有继承,没有数据元素的类。 + +```cpp +class Empty{ +public: + void print() { + std::cout<<"I am Empty class"< +bool isSame( T const & t1, T const & t2 ) +{ + return &t1 == &t2; +} +``` + +我们来测试一下: + +```cpp +int main() { + Empty a,b; + assert(!isSame(a,b)); // 编译通过,a与b的地址不同 + + Empty *p=new Empty; + Empty *q=new Empty; + assert(!isSame(p,q)); // 编译通过,a与b的地址不同 + return 0; +} +``` + +上面测试了,两个不同对象地址是不同的,考虑下面场景: + +```cpp +Empty a,b; +// 在同一地址具有两个对象将意味着在使用指针引用它们时将无法区分这两个对象。 +Empty *p1=&a; +p1->print(); +``` + +此时会发现,如果a的地址与b的地址一样,那么在同一地址具有两个对象将意味着在使用指针引用它们时将无法区分这两个对象。因此两个不同对象的地址不同。 + +## 2.空基类优化 + +现在对比一下下面两个用法,第一种,一个类中包含了两一个类作为成员,然后通过这个来获得被包含类的功能。 + +```cpp +class notEbo { + int i; + Empty e; + // do other things +}; +``` + +另一种直接采用继承的方式来获得基类的成员函数及其他功能等等。 + +```cpp +class ebo:public Empty { + int i; + // do other things +}; +``` + +接下来做个测试: + +```cpp +std::cout< +__gnu_cxx::bitmap_allocator<_Tp> +__gnu_cxx::bitmap_allocator<_Tp> +__gnu_cxx::__mt_alloc<_Tp> +__gnu_cxx::__pool_alloc<_Tp> +__gnu_cxx::malloc_allocator<_Tp> +``` + +那这和我们的EBO有啥关系呢? + +实际上,上面所列出继承的基类都是内存管理的EBO(空基类)。 + +在每个容器中的使用都是调用每个内存管理的`rebind<_Tp>::other`。 + +例如红黑树源码结构: + +```cpp +typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template + rebind<_Rb_tree_node<_Val> >::other _Node_allocator; +struct _Rb_tree_impl : public _Node_allocator +{ +// do somethings +}; +``` + +接下来我们看上面列出的内存管理类里面的源码结构:这里拿`allocator`举例: + +```cpp +template +class allocator: public __allocator_base<_Tp> +{ + template + struct rebind { typedef allocator<_Tp1> other; }; +}; +``` + +看到了没,通过`rebind<_Tp>::other`来获得传递进来的内存分配器,也就是前面提到的这些。 + +```cpp +std::allocator<_Tp> +__gnu_cxx::bitmap_allocator<_Tp> +__gnu_cxx::bitmap_allocator<_Tp> +__gnu_cxx::__mt_alloc<_Tp> +__gnu_cxx::__pool_alloc<_Tp> +__gnu_cxx::malloc_allocator<_Tp> +``` + +搞懂了这些,来测试一波: + +```cpp +void print() { + cout<)<<" "<::rebind::other)<)<<" "<::rebind::other)<)<<" "<::rebind::other)<)<<" "<::rebind::other)<)<<" "<::rebind::other)<)<<" "<::rebind::other)< +class MyContainerNotEBO { + T *data_ = nullptr; + std::size_t capacity_; + Allocator allocator_; // 嵌入一个MyAllocator +public: + MyContainerNotEBO(std::size_t capacity) + : capacity_(capacity), allocator_(), data_(nullptr) { + std::cout << "alloc malloc" << std::endl; + data_ = reinterpret_cast(allocator_.allocate(capacity * sizeof(T))); // 分配内存 + } + + ~MyContainerNotEBO() { + std::cout << "MyContainerNotEBO free malloc" << std::endl; + allocator_.deallocate(data_); + } +}; +``` + +第二种方式:采用空基类优化,继承来获得内存管理功能 + +```cpp +template +class MyContainerEBO + : public Allocator { // 继承一个EBO + T *data_ = nullptr; + std::size_t capacity_; +public: + MyContainerEBO(std::size_t capacity) + : capacity_(capacity), data_(nullptr) { + std::cout << "alloc malloc" << std::endl; + data_ = reinterpret_cast(this->allocate(capacity * sizeof(T))); + } + + ~MyContainerEBO() { + std::cout << "MyContainerEBO free malloc" << std::endl; + this->deallocate(data_); + } +}; +``` + +开始测试: + +```cpp +int main() { + MyContainerNotEBO notEbo = MyContainerNotEBO(0); + std::cout << "Using Not EBO Test sizeof is " << sizeof(notEbo) << std::endl; + MyContainerEBO ebo = MyContainerEBO(0); + std::cout << "Using EBO Test sizeof is " << sizeof(ebo) << std::endl; + + return 0; +} +``` + +测试结果: + +```cpp +alloc malloc +Using Not EBO Test sizeof is 24 +alloc malloc +Using EBO Test sizeof is 16 +MyContainerEBO free malloc +MyContainerNotEBO free malloc +``` + +我们发现采用EBO的设计确实比嵌入设计好很多。至此,本节学习完毕。 +