This commit is contained in:
Light-City 2019-11-05 17:16:16 +08:00
parent d75bab0a74
commit 518fa48682
16 changed files with 6474 additions and 2 deletions

View File

@ -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++调试!全部可正常运行!
## 关于作者:

146
stl_src/array.md Normal file
View File

@ -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 <tr1/array>
std::tr1::array<int ,10> a;
```
tr1中的array比较简单模拟语言本身的数组并且让其支持迭代器操作使其同其他容器一样能够调用算法。对于tr1中array没有构造与析构。迭代器是直接使用传递进来的类型定义指针。
简单的看一下这个静态数组array源码
```cpp
template<typename _Tp, std::size_t _Nm>
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<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
}
```
里面使用`reverse_iterator`作为rbegin与rend操作的迭代器。
看上去上面一个迭代器实际上两个还有一个iterator,这个直接使用传递进来的类型定义指针,作为迭代器。
可以将其对比为vector中的正向与反向迭代器。
值得注意的是在tr1::array中支持传递数组大小为0例如我们使用如下
```
std::tr1::array<int,0> a;
```
对于这样的写法,会对应到下面:
```
// Support for zero-sized arrays mandatory.
value_type _M_instance[_Nm ? _Nm : 1];
```
根据传递进来的大小如果不为0就是传递进来的大小否则为1。
## 2.std::array
使用
```
std::array<int ,10> a;
```
std中的array包含了
![std_array.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/std_array.png)
对比tr1与std的array
```cpp
template<typename _Tp, std::size_t _Nm>
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<iterator> reverse_iterator;
typedef std::reverse_iterator<const_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<typename _Tp, std::size_t _Nm>
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<typename _Tp, std::size_t _Nm>
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<typename _Tp>
struct __array_traits<_Tp, 0>
{
struct _Type { };
static constexpr _Tp&
_S_ref(const _Type&, std::size_t) noexcept
{ return *static_cast<_Tp*>(nullptr); }
};
```

506
stl_src/deque.md Normal file
View File

@ -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 _Tp, typename _Alloc>
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 _Tp, typename _Alloc>
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<size_type>(__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;
}
```

567
stl_src/hashtable.md Normal file
View File

@ -0,0 +1,567 @@
# C++ STL源码剖析之哈希表
## 0.导语
哈希表,是作为`unordered_map`与`undered_set`等的底层容器自gcc2.9后源码量大增!
这次阅读的代码仍旧是gcc4.9.1,代码量非常多,就不全部展开,重点研究底层哈希的艺术与技术,似乎这两个词语很押韵哦,哈哈,进入正文~
## 1.Hashtable初识
先来看一眼Hashtable源码
```cpp
template<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash,
typename _RehashPolicy, typename _Traits>
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<typename __alloctr_rebind<_Alloc,__detail::_Hash_node<_Value,_Traits::__hash_cached::value> >::__type>
{
};
```
没学过类模板的一脸懵逼,数一下模板参数都晕死。。。
还有它的继承,一下子整出这么多父亲来。。。
下面就来一一分析它的父亲,然后再回到哈希表。
## 2._Hashtable_base
其中注释中如下:
> Helper class adding management of _Equal functor to _Hash_code_base type.
帮助程序类将仿函数_Equal的管理添加到_Hash_code_base中。
对比代码就可以看出来是啥意思了:
```cpp
template<typename _Key, typename _Value,
typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash, typename _Traits>
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<typename _Key, typename _Value, typename _ExtractKey,
typename _H1, typename _H2, typename _Hash,bool __cache_hash_code>
struct _Hash_code_base;
```
根据是否缓存,得到其偏特化版本:
- 使用范围哈希(实际上就是我们通常说的除留余数法)不缓存hash code。
```cpp
template<typename _Key, typename _Value, typename _ExtractKey,
typename _H1, typename _H2, typename _Hash>
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<typename _Key, typename _Value, typename _ExtractKey,
typename _H1, typename _H2, typename _Hash>
struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash, true>;
```
- 有哈希函数以及范围哈希函数不缓存hash code。
```cpp
template<typename _Key, typename _Value, typename _ExtractKey,
typename _H1, typename _H2>
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<typename _Key, typename _Value, typename _ExtractKey,
typename _H1, typename _H2>
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<key>
```
下面这个就是:
```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<std::size_t>(__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<std::size_t>(__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<typename _Value>
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<typename _Value, bool _Cache_hash_code>
struct _Hash_node;
/**
* Specialization for nodes with caches, struct _Hash_node.
*
* Base class is __detail::_Hash_node_value_base.
*/
template<typename _Value>
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<typename _Value>
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<typename _Value, bool _Cache_hash_code>
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<typename _Value, bool __constant_iterators, bool __cache>
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<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash,
typename _RehashPolicy, typename _Traits>
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<float>(size()) / static_cast<float>(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计算。

335
stl_src/iterator.md Normal file
View File

@ -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 IteratorBidirectional Iterator和Random Access Iterator的对应实现。
```c++
template<class Iterator>
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<class inputIterator, class distance>
inline void __advance(inputIterator&i, distance n,
input_iterator_tag) {
std::cout << "input tag" << std::endl;
}
// output iterator
template<class outputIterator, class distance>
inline void __advance(outputIterator&i, distance n,
output_iterator_tag) {
std::cout << "output tag" << std::endl;
}
// forward iterator
template<class ForwardIterator, class Distance>
inline void __advance(ForwardIterator &i, Distance n,
forward_iterator_tag) {
std::cout << "forward tag" << std::endl;
}
// bidrectional iterator
template<class BidiectionalIterator, class Distance>
inline void __advance(BidiectionalIterator &i, Distance n,
bidiectional_iterator_tag) {
std::cout << "bidrectional tag" << std::endl;
}
// RandomAccess iterator
template<class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator &i, Distance n,
random_access_iterator_tag) {
std::cout << "randomaccess tag" << std::endl;
}
```
定义萃取机:
```c++
// traits 型别
template<class I>
struct Iterator_traits {
typedef typename I::iterator_category iterator_category;
};
// 针对原生指针设计的"偏特化版"
template<class I>
struct Iterator_traits<I *> {
typedef random_access_iterator_tag iterator_category;
};
template<class I>
struct Iterator_traits<const I *> {
typedef random_access_iterator_tag iterator_category;
};
```
对外暴露接口:
```c++
// 对外接口
template<class InputIterator, class Distance>
inline void advance(InputIterator &i, Distance n) {
// 通过Ierator_traits询问它的iterator_category是谁
typedef typename Iterator_traits<InputIterator>::iterator_category category;
__advance(i, n, category()); // 各型别的重载
}
```
定义class type
```c++
// class type
template<class Category>
struct iterator {
typedef Category iterator_category;
};
```
开始测试我们使用上述定义的class type与原生指针来测试分别进入萃取机的普通萃取机与偏特化萃取机看看是否得到相应的Tag。
```c++
int main() {
iterator<input_iterator_tag> input;
iterator<output_iterator_tag> output;
iterator<forward_iterator_tag> forward;
iterator<bidiectional_iterator_tag> bidect;
iterator<random_access_iterator_tag> 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<class InputIterator, class Distance>
inline typename Iterator_traits<InputIterator>::iterator_category
advance(InputIterator &i, Distance n) {
// 通过Ierator_traits询问它的iterator_category是谁
typedef typename Iterator_traits<InputIterator>::iterator_category category;
return __advance(i, n, category()); // 各型别的重载
}
```
紧接着修改`__advance`返回:
```c++
// input iterator
template<class inputIterator, class distance>
inline typename Iterator_traits<inputIterator>::iterator_category
__advance(inputIterator &i, distance n,
input_iterator_tag) {
std::cout << "input tag" << std::endl;
return input_iterator_tag();
}
// output iterator
template<class outputIterator, class distance>
inline typename Iterator_traits<outputIterator>::iterator_category
__advance(outputIterator &i, distance n,
output_iterator_tag) {
std::cout << "output tag" << std::endl;
return output_iterator_tag();
}
// forward iterator
template<class ForwardIterator, class Distance>
inline typename Iterator_traits<ForwardIterator>::iterator_category
__advance(ForwardIterator &i, Distance n,
forward_iterator_tag) {
std::cout << "forward tag" << std::endl;
return forward_iterator_tag();
}
// bidrectional iterator
template<class BidiectionalIterator, class Distance>
inline typename Iterator_traits<BidiectionalIterator>::iterator_category
__advance(BidiectionalIterator &i, Distance n,
bidiectional_iterator_tag) {
std::cout << "bidrectional tag" << std::endl;
return bidiectional_iterator_tag();
}
// RandomAccess iterator
template<class RandomAccessIterator, class Distance>
inline typename Iterator_traits<RandomAccessIterator>::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_tag> input;
iterator<output_iterator_tag> output;
iterator<forward_iterator_tag> forward;
iterator<bidiectional_iterator_tag> bidect;
iterator<random_access_iterator_tag> 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<typename _Iterator>
struct iterator_traits
{
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
```
3`iterator`
上面提到的class type为下面的简单版对比一下没有啥区别就是模板参数多了一些typedef多了。
```c++
template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,
typename _Pointer = _Tp*, typename _Reference = _Tp&>
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源码奥秘如侯捷老师所说**源码面前了无秘密。**

1278
stl_src/list.md Normal file

File diff suppressed because it is too large Load Diff

330
stl_src/map_multimap.md Normal file
View File

@ -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就是valuevalue就是key。
- map的key不可修改,map与multimap的插入调用函数不同影响了其key是否对应value。
- initializer_list使用
- map有`[]`操作符而multimap没有`[]`操作符。
## 1.map
> key为keyvalue为key+data
下面map中我们可以看到value_type为一个pair。
```cpp
template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{
public:
typedef _Key key_type;
typedef _Tp mapped_type;
typedef std::pair<const _Key, _Tp> value_type;
typedef _Compare key_compare;
typedef _Alloc allocator_type;
private:
// key为key,value为key+data
typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
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<typename _Pair>
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<typename _T1>
struct mySelect1st
: public unary_function<_T1, typename _T1::first_type>
{
template<typename _T2>
typename _T2::first_type&
operator()(_T2& __x) const
{ return __x.first; }
};
int main() {
typedef pair<const int,int> value_type;
_Rb_tree<int, value_type, mySelect1st<value_type>, less<int>> it;
it._M_insert_unique(make_pair(1,3));
it._M_insert_unique(make_pair(3,6));
for(auto each:it)
cout<<each.first<<" "<<each.second<<endl;
}
```
> key不能改data可以改
上述源码中自动为key添加一个const所以key不能改。
```cpp
typedef std::pair<const _Key, _Tp> value_type;
```
## 2.insert
> insert里面采用`_M_insert_unique`
insert的几种方法
1 插入 pair
```cpp
std::pair<iterator, bool> 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<typename _InputIterator>
void
insert(_InputIterator __first, _InputIterator __last)
{ _M_t._M_insert_equal(__first, __last); }
```
4从list中插入
```cpp
void
insert(initializer_list<value_type> __l)
{ this->insert(__l.begin(), __l.end()); }
```
针对最后一个insert里面有个`initializer_list`,举个例子大家就知道了。
## 3.initializer_list使用
> 实际编程实践
```cpp
vector<int> v={1,2,3}; // 底层调用vector的构造函数
v={2,5,6}; // 底层调用vector的=操作符
initializer_list<int> ll={4,5,6};
v.insert(v.begin(),ll); // 底层调用下面insert函数
for(auto x:v) cout<<x<<" ";
cout<<endl;
vector<int> vv(ll); // 底层调用vector的构造函数
vector<string> city{"Berlin", "New York", "London", "Cairo","Tokyo", "Cologne"};
```
针对这个vector初始化大家很熟悉了为何可以这样初始化呢
我们看一下vector源码
```cpp
vector&
operator=(initializer_list<value_type> __l)
{
this->assign(__l.begin(), __l.end());
return *this;
}
iterator
insert(const_iterator __position, initializer_list<value_type> __l)
{ return this->insert(__position, __l.begin(), __l.end()); }
vector(initializer_list<value_type> __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<value_type> __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<value_type> __l)
{
this->clear();
this->insert(__l.begin(), __l.end());
return *this;
}
// insert插入
void
insert(std::initializer_list<value_type> __list)
{ insert(__list.begin(), __list.end()); }
```
> 实际编程实践
map使用`initializer_list`(set使用一样)
```cpp
// 这里要注意pair的first参数必[须是const
initializer_list<pair<const strin[g,int>> l = {{"hello", 1}, {"world", 2}};
map<string,int> mm(l); // map构造函数
map<string, int> m2{{"hello", 1}, {"world", 2}}; // map构造函数
map<string, int> m1={{"hello", 1}, {"world", 2}}; // map构造函数
m1 = {{"h", 1}, {"w", 3}}; // 底层调用map的=操作符
map<string, int> mp;
mp.insert(l); // insert插入[
```
上述会引出另一个问题:
```cpp
map<string, int> m1={{"hello", 1}, {"world", 2}}; // map构造函数
m1 = {{"h", 1}, {"w", 3}}; // 底层调用map的=操作符
```
这两个为何一个调用的构造,一个调用=操作符呢?
在初始化的时候,定义及赋值的时候就直接调用构造,后面再次赋值,就是先调用拷贝构造(有可能会被编译器优化),再调用=操作符。
> 实例分析
下面用一个具体实例来分析一下:
```cpp
template <typename _Tp>
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<<"调用了=操作符重载"<<endl;
return *this;
}
};
```
调用:
```cpp
Foo<int> 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 _Key, typename _Tp,
typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class multimap
{
public:
typedef std::pair<const _Key, _Tp> value_type;
}
```
下面使用multimap与rbtree两种方式来联系。
```cpp
multimap<int, string> c;
c.insert(make_pair(1,"asdqw"));
c.insert(make_pair(1,"qweq"));
for(auto each:c) cout<<each.first<<" "<<each.second<<endl;
typedef pair<const int,string> valueT;
_Rb_tree<int, valueT, _Select1st<valueT>, less<int>> mulm;
mulm._M_insert_equal(make_pair(2,"abc"));
mulm._M_insert_equal(make_pair(2,"cde"));
for(auto each:mulm)
cout<<each.first<<" "<<each.second<<endl;
```
输出:
```
1 asdqw
1 qweq
2 abc
2 cde
```
## 5.map与multimap的重要操作符
map与multimap`[]`操作符map的`[]`操作符返回传递给map的第二个参数。
传递给`[]`一个key如果查找到就是value否则就是默认值0。
```cpp
mapped_type&
operator[](const key_type& __k)
{
iterator __i = lower_bound(__k);// 找到__k第一个。
// __i->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<const key_type&>(__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源码中没有重载这个操作符

397
stl_src/myhashtable.md Normal file
View File

@ -0,0 +1,397 @@
# 从0到1打牢算法基础之手写一个哈希表
## 0.导语
目的手写实现一个哈希表采用拉链法构建每个hash(key)对应的是一个红黑树。
看起来很简单但可以学到很多东西。实现语言C++。
为了打牢算法基础github开了个仓库来完成后面算法基础的应用与实现地址
> https://github.com/Light-City/algPratice
也可以点击原文阅读。
## 1.简易版哈希表
我们将哈希表封装在一个类中,完成遍历的定义与声明以及构造、析构的实现:
```cpp
template<typename Key, typename Value>
class HashTable {
private:
const static int upperTol = 3;
const static int lowerTol = 1;
const static int initCapacity = 1;
map<Key, Value> **hashtable;
int M;
int size;
public:
/**
* 传参构造
* @param M
*/
HashTable(int M) : M(M), size(0) {
// 这里的括号是为了初始化为0,这就可以不用写下面的代码,当然在后面add之类的操作,就不需要动态分配内存.
// this->hashtable = new map<Key, Value> *[M]();
this->hashtable = new map<Key, Value> *[M];
for (int i = 0; i < M; i++) {
this->hashtable[i] = new map<Key, Value>;
}
}
/**
* 默认构造
*/
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<Key> h;
return (h(key) & 0x7fffffff) % M;
}
```
这里使用std的hash得到值之后将其`&`上`0x7fffffff`去掉高位的负号转为正数然后余上M。
现在有了这些我们来实现一下它的增删改查。
> 增操作
底层采用的是红黑树,插入是使用insert方法通过构造一个pair来完成。
而当key存在的时候更新值即可对于更新这一块如果直接使用insert是不起作用的比如下面测试
```cpp
map<string,int> m{{"a",1},{"b",2}};
for(auto i:m) cout<<i.first<<" "<<i.second<<" ";
cout<<endl;
m.insert({{"a",2}});
for(auto i:m) cout<<i.first<<" "<<i.second<<" ";
cout<<endl;
m["a"]=2;
for(auto i:m) cout<<i.first<<" "<<i.second<<" ";
cout<<endl;
```
输出:
```cpp
a 1 b 2
a 1 b 2
a 2 b 2
```
因此如果要修改key对应的value可以通过`[]`来修改,还可以先删除,再插入,这里就用这个方法。
```cpp
/**
* 添加新元素
* @param key
* @param value
*/
void add(Key key, Value value) {
// 拉链法出来的map如果为空,就动态分配一个map,然后进行插入
// 如果key不存在就看内存是否存在,不存在,就分配,存在就插入
if (hashtable[hashFunc(key)] == NULL || hashtable[hashFunc(key)]->count(key) == 0) {
if (hashtable[hashFunc(key)] == NULL)
hashtable[hashFunc(key)] = new map<Key, Value>;
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<Key, Value> **newHashTable = new map<Key, Value> *[newM];
for (int i = 0; i < newM; i++) {
newHashTable[i] = new map<Key, Value>;
}
int oldM = M;
this->M = newM;
for (int i = 0; i < oldM; i++) {
map<Key, Value> 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<typename K, typename V>
// 重载<<操作符
friend ostream &operator<<(ostream &out, HashTable<K, V> &hashTable);
```
定义:
```cpp
template<typename K, typename V>
ostream &operator<<(ostream &out, HashTable<K, V> &hashTable) {
hashTable.print();
return out;
}
```
至此,上述哈希表实现完毕,现在来测试:
```cpp
#include "hash.h"
#include <vector>
int main() {
vector<string> words{"java", "c++", "c", "c++", "c#", "python", "ruby", "python",
"c", "c", "c++", "java", "c++", "rust", "python"};
HashTable<string, int> ht(1);
for (string word : words) {
if (ht.contains(word)) {
ht.set(word, ht.get(word) + 1);
} else {
ht.add(word, 1);
}
}
cout<<ht;
cout<<"size="<<ht.getSize()<<",maxCapacity="<<ht.maxCapacity()<<",minCapacity="<<ht.minCapacity()<<endl;
string w="c++";
ht.remove(w);
if (ht.contains(w))
cout << "" << w << ": " << ht.get(w) << endl;
else
cout << "No word " << w << " in words" << endl;
cout<<ht;
ht.remove("c#");
ht.remove("java");
ht.remove("c");
cout<<"size="<<ht.getSize()<<",maxCapacity="<<ht.maxCapacity()<<",minCapacity="<<ht.minCapacity()<<endl;
cout<<ht;
return 0;
}
```
输出结果:
```cpp
resize 2
resize 4
{c#:1,java:2,ruby:1,c:3,rust:1,python:3,c++:4}
size=7,maxCapacity=12,minCapacity=4
No word c++ in words
{c#:1,java:2,ruby:1,c:3,rust:1,python:3}
resize 2
size=3,maxCapacity=6,minCapacity=2
{python:3,ruby:1,rust:1}
```
至此,完成了一个简单的哈希表。
## 1.优化哈希表
在gcc2.9版本中,底层的哈希表是以素数作为容量动态修改的,因此这里的优化从这里出发:
类内部开头添加下面数组:
```cpp
// 素数数组
const vector<int> capacity = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317,
196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
```
去掉带参数的构造函数,修改默认构造为:
```cpp
/**
* 默认构造
* @param M
*/
HashTable() {
M = capacity[capacityIndex], size = 0;
// 这里的括号是为了初始化为0,这就可以不用写下面的代码,当然在后面add之类的操作,就不需要动态分配内存.
// this->hashtable = new map<Key, Value> *[M]();
this->hashtable = new map<Key, Value> *[M];
for (int i = 0; i < M; i++) {
this->hashtable[i] = new map<Key, Value>;
}
}
```
修改add函数
在size++后添加下面代码:
```cpp
if (size >= maxCapacity() && capacityIndex + 1 < capacity.size()) {
capacityIndex++;
resize(capacity[M]);
}
```
每次resize从capacity中取值。
> remove函数修改
在size--后修改:
```cpp
if (size < minCapacity() && capacityIndex - 1 >= 0) {
capacityIndex--;
resize(capacityIndex);
}
```
至此,哈希表完成!测试代码同上。

244
stl_src/queue_stack.md Normal file
View File

@ -0,0 +1,244 @@
# C++ STL源码剖析之容器配接器stack与queue、priority_queue
## 0.导语
为何stack与queue不被称为容器呢
下面本节带着这个问题来深入源码分析。
## 1.stack
在stack的源码中我们关注两点
- 默认`_Sequence`为`deque`
- 内部函数实现是调用`_Sequence`对应容器的函数。
![stack.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/stack.png)
```cpp
template<typename _Tp, typename _Sequence = deque<_Tp> >
class stack
{
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 this name.
_Sequence c;
public:
reference
top()
{
__glibcxx_requires_nonempty();
return c.back();
}
void
push(const value_type& __x)
{ c.push_back(__x); }
}
```
> 测试stack底层容器
对于stack来说底层容器可以是`vector`、`deque`、`list`,但不可以是`map`、`set`。
由于编译器不会做全面性检查当调用函数不存在的时候就编译不通过所以对于像set虽然不能作为底层容器但如果具有某些函数调用仍然是成功的直到调用的函数不存在。
```cpp
int test_stack() {
cout<<"============test_stack============="<<endl;
clock_t timeStart = clock();
stack<int, list<int>> 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<int, deque<int>> 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<int, vector<int>> 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<typename _Tp, typename _Sequence = deque<_Tp> >
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 _Tp, typename _Sequence = vector<_Tp>,
typename _Compare = less<typename _Sequence::value_type> >
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============="<<endl;
clock_t timeStart = clock();
queue<int, list<int>> 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<int, deque<int>> 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============="<<endl;
clock_t timeStart = clock();
priority_queue<int, deque<int>> 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<int, vector<int>> 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不被称为容器 把它称为容器配接器。

621
stl_src/rb_tree.md Normal file
View File

@ -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<typename _Val>
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<typename _Tp>
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<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc = allocator<_Val> >
class _Rb_tree
{
protected:
typedef _Rb_tree_node_base* _Base_ptr;
template<typename _Key_compare,
bool _Is_pod_comparator = __is_pod(_Key_compare)>
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<value_type> iterator;
typedef std::reverse_iterator<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.1x的叔叔节点y是黑色且x是一个右孩子
![rb1.2.1.png](https://raw.githubusercontent.com/Light-City/cloudimg/master/rb1.2.1.png)
情况1.2.2x的叔叔节点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.1x的叔叔节点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<map>或者<set>
```
类定义:
```
_Rb_tree<int, int, _Identity<int>, less<int>> itree;
```
然后调用相应函数即可。

214
stl_src/set_multiset.md Normal file
View File

@ -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 _Key, typename _Compare = std::less<_Key>,
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_type, value_type, _Identity<value_type>,
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 _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set
{
private:
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
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<typename _InputIterator>
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<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
template<typename _Arg>
#endif
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::iterator, bool>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_insert_unique( _Arg && __v )
{
typedef pair<iterator, bool> _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<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_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: "<<itree._M_insert_unique(5).second<<endl; // 学习返回值
typedef pair<int ,bool> _Res; // 也来用一下typedef后的pair
cout<<_Res(1,true).first<<endl; // 直接包裹
_Res r=make_pair(2,false); // 定义新对象
cout<<r.first<<endl; // 输出结果
```
## 2.multiset
同理,multiset与set定义基本类似不同之处在于插入使用的是另一个函数,这样才使它能够完成重复key的插入
```cpp
template<typename _InputIterator>
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<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_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重复如何都可以插入进去。

256
stl_src/traits.md Normal file
View File

@ -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<class _Tp>
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 <class I>
inline
void func(I iter) {
func_impl(iter, *iter); // 传入iter和iter所指的值class自动推导
}
```
通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型。
```c++
template <class I, class T>
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 <class I, class T>
T func_impl(I iter, T t) {
T tmp; // 这里就是迭代器所指物的类别
// ... 功能实现
}
template <class T>
(*T) func(T t) { // !!!Wrong code
return func_impl(t, *t); // forward the task to func_impl
}
int main() {
int i =10;
cout<<func(&i)<<endl; // !!! Cant pass compile
}
```
如果去编译上述代码,编译失败!
这个问题解决起来也不难,声明内嵌型别似乎是个好主意,这样我们就可以直接获取。只要做一个 iterator然后在定义的时候为其指向的对象类型制定一个别名就好了像下面这样
```c++
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
template <class I>
typename I::value_type
func(I ite) {
std::cout << "class version" << std::endl;
return *ite;
}
int main() {
// ...
MyIter<int> ite(new int(8));
cout << func(ite); // 输出8
}
```
很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为 func 如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:
```c++
int *p = new int(5);
cout<<func(p)<<endl; // error
```
我们的 func 无法支持原生指针这显然是不能接受的。此时template partial specialization 就派上了用场。
## 3.救世主 Traits
前面也提到了,如果直接使用`typename I::value_type`,算法就无法接收原生指针,因为原生指针根本就没有 value_type 这个内嵌类型。
因此,我们还需要加入一个中间层对其进行判断,看它是不是原生指针,注意,这就是 traits 技法的妙处所在。
如果我们只使用上面的做法,也就是内嵌 value_type那么对于没有 value_type 的指针,我们只能对其进行偏特化,这种偏特化是针对可调用函数 func 的偏特化,假如 func 有 100 万行行代码,那么就会造成极大的视觉污染。
**1函数偏特化**
函数偏特化:
```c++
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
template <class I>
typename I::value_type
func(I ite) {
std::cout << "class version" << std::endl;
return *ite;
}
template <class I>
I
func(I* ite) {
std::cout << "pointer version" << std::endl;
return *ite;
}
template <class I>
I func(const I* ite) {
std::cout << "const pointer version" << std::endl;
return *ite;
}
int main() {
// ...
MyIter<int> ite(new int(8));
cout << func(ite)<<endl;
int *p = new int(52);
cout<<func(p)<<endl;
const int k = 3;
cout<<func(&k)<<endl;
}
```
输出:
```c++
class version
8
pointer version
52
const pointer version
3
```
**2加入中间层**
在 STL 中 Traits 是什么呢?看下图:
![](https://raw.githubusercontent.com/Light-City/cloudimg/master/trai.png)
利用一个中间层`iterator_traits`固定了`func`的形式,使得重复的代码大量减少,唯一要做的就是稍稍特化一下 iterator_tartis 使其支持 pointer 和 const pointer:)
```c++
#include <iostream>
template <class T>
struct MyIter {
typedef T value_type; // 内嵌型别声明
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
// class type
template <class T>
struct iterator_traits {
typedef typename T::value_type value_type;
};
// 偏特化1
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};
// 偏特化2
template <class T>
struct iterator_traits<const T*> {
typedef T value_type;
};
template <class I>
typename iterator_traits<I>::value_type
// 首先询问iterator_traits<I>::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<int> ite(new int(8));
std::cout << func(ite)<<std::endl;
int *p = new int(52);
std::cout<<func(p)<<std::endl;
const int k = 3;
std::cout<<func(&k)<<std::endl;
}
```
上述的过程是首先询问`iterator_traits<I>::value_type`,如果传递的 I 为指针,则进入特化版本,`iterator_traits`直接回答`T`;如果传递进来的`I`为`class type`,就去询问`T::value_type`.
上述的通俗解释为算法(func)问 iterator_traits(我),但是 iterator_traits(我)发现手上是指针的时候,就由我来替它回答。如果是 class typeiterator_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

137
stl_src/typename.md Normal file
View File

@ -0,0 +1,137 @@
# typename
STL底层源码有下面几行,typedef与typename联用,这几个看着好复杂,究竟啥意思,我们今天一起来剖析!
```c++
template<typename _Iterator>
struct iterator_traits
{
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
```
## typename的常见用法
首先学习一下typename的常见用法:
```c++
template <typename T>
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()"<<endl; return 100;} //静态函数
typedef int C; //嵌套类型
struct A1 { //嵌套类型
static int s;
};
};
```
调用的时候,可以直接调:
```c++
cout<<MyClass::A<<endl;
cout<<MyClass::B()<<endl;
MyClass:C c;
...
```
## 完整例子尝试
让我们回到一个typename的例子:
```c++
template <class T>
void foo() {
T::iterator * iter;
// ...
}
```
这段代码的目的是什么?多数人第一反应可能是:作者想定义一个指针`iter`,它指向的类型是包含在类作用域`T`中的`iterator`。可能存在这样一个包含`iterator`类型的结构:
```c++
struct MyIterator {
struct iterator {
};
};
```
调用如下:
```c++
foo<MyIterator>();
```
这样一来,`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 <class T>
void foo() {
typename T::iterator * iter;
}
```
这样编译器就可以确定`T::iterator`是一个类型,而不再需要等到实例化时期才能确定,因此消除了前面提到的歧义。
### 剖析源码
回到STL源码
```c++
template<typename _Iterator>
struct iterator_traits
{
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
```
看到上面的,我们就一下子清楚了,无非就是使用typename告诉编译器`_Iterator::iterator_category`是一个类型,然后使用typedef重命名一下,其余类似!

356
stl_src/unordered_map.md Normal file
View File

@ -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 _Key, class _Tp,
class _Hash = hash<_Key>,
class _Pred = std::equal_to<_Key>,
class _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class unordered_map
{
typedef __umap_hashtable<_Key, _Tp, _Hash, _Pred, _Alloc> _Hashtable;
_Hashtable _M_h;
};
```
去看底层容器的`__umap_hashtable`的声明:
```cpp
template<bool _Cache>
using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>;
template<typename _Key,
typename _Tp,
typename _Hash = hash<_Key>,
typename _Pred = std::equal_to<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> >,
typename _Tr = __umap_traits<__cache_default<_Key, _Hash>::value>>
using __umap_hashtable = _Hashtable<_Key, std::pair<const _Key, _Tp>,
_Alloc, __detail::_Select1st,
_Pred, _Hash,
__detail::_Mod_range_hashing,
__detail::_Default_ranged_hash,
__detail::_Prime_rehash_policy, _Tr>;
```
可以得到下面结论:
hashtable的模板参数
```cpp
template<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash,
typename _RehashPolicy, typename _Traits>
```
默认情况下undered_map采用
- H1为hash<key>
- 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<bool _Cache>
using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>;
```
可以对比上述两个发现__umap_traits里面有一串__cache_default我们再看一下模板参数为bool类型故可以打印出来是false还是true经过实测为false表示不缓存hash code。
```cpp
template<typename _Tp, typename _Hash>
using __cache_default
= __not_<__and_<__is_fast_hash<_Hash>,
__detail::__is_noexcept_hash<_Tp, _Hash>>>;
```
继续看`__umap_traits`,这个实际上是调用`_Hashtable_traits`,我们继续往下:
```cpp
template<bool _Cache_hash_code, bool _Constant_iterators, bool _Unique_keys>
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<bool _Cache>
using __ummap_traits = __detail::_Hashtable_traits<_Cache, false, false>;
```
小结在上面分析我们知道了unordered_map与unordered_multimap的本质区别也发现了如何在底层源码上用一个容器实现两个容器适配器
## 2.undered_set与unordered_multiset本质区别
分析同前面一样先看undered_set:
```cpp
template<class _Value,
class _Hash = hash<_Value>,
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<bool _Cache>
using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>;
template<typename _Value,
typename _Hash = hash<_Value>,
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<bool _Cache>
using __umset_traits = __detail::_Hashtable_traits<_Cache, true, false>;
```
再将两者对比一下本质就是undered_set不允许key重复而undered_multiset允许key重复。
## 3.三大结论
现在,我们有了前面基础,依次列出前面四个容器适配器:
(1) undered_map
```cpp
template<bool _Cache>
using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>;
```
(2) undered_multimap
```cpp
template<bool _Cache>
using __umap_traits = __detail::_Hashtable_traits<_Cache, false, false>;
```
(3) undered_set
```cpp
template<bool _Cache>
using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>;
```
(4) undered_set
```cpp
template<bool _Cache>
using __uset_traits = __detail::_Hashtable_traits<_Cache, true, false>;
```
对比后,得出
- 结论1undered_map与undered_set不允许key重复,而带multi的则允许key重复
- 结论2undered_map与undered_multimap采用的迭代器是iterator而undered_set与undered_multiset采用的迭代器是const_iterator。
- 结论3undered_map与undered_multimap的key是keyvalue是key+value而undered_set与undered_multiset的key是ValueValue也是Key。
最后一个结论对比看下面(我们看传递给hashtable的第一与第二个参数)
undered_map与undered_multimap
```cpp
using __umap_hashtable = _Hashtable<_Key,
std::pair<const _Key, _Tp>,
_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 _Value,
typename _Hash = hash<_Value>,
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<string,int> um;
hash<string> h;
cout<<um.hash_function()("hhhhhawq")<<endl;
cout<<h("hhhhhawq")<<endl;
```
输出:
```cpp
9074142923776869151
9074142923776869151
```
进一步验证了采用默认的hash。
> 是否空、大小、最大大小
```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<iterator, bool>
insert(const value_type& __x)
{ return _M_h.insert(__x); }
// pair
std::pair<iterator, bool>
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<typename _InputIterator>
void
insert(_InputIterator __first, _InputIterator __last)
{ _M_h.insert(__first, __last); }
// 初始化列表插入
void
insert(initializer_list<value_type> __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<string,int> um;
cout<<um.hash_function()("hhhhhawq")<<endl; //传递的内容要与上面key类型一致
```
> key_eq
key_eq返回的是std::equal_to
使用如下:
```cpp
unordered_map<string,int> um;
cout<<um.key_eq()("1","2")<<endl;
```
> 查找与获取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一样的函数所以就不阐述了。

785
stl_src/vector.md Normal file
View File

@ -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<typename _Tp>
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<typename _Up>
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<allocator<_Tp>>::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<allocator<_Tp>>::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<typename _Tp, typename _Alloc>
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<const _Tp_alloc_type *>(&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<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class vector : protected _Vector_base<_Tp, _Alloc> {
}
```
vector代码中使用基类的内存函数及typedef等
```cpp
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
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 <pointer, vector> iterator;
typedef __gnu_cxx::__normal_iterator <const_pointer, vector> const_iterator;
```
反向:
```cpp
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
```
`__normal_iterator`与`reverse_iterator`都定义于stl_iterator.h封装了vector元素的指针。
### 2.1 正向
```cpp
template<typename _Iterator, typename _Container>
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<typename _Iter>
__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<typename _Iterator>
class reverse_iterator
: public iterator<typename iterator_traits<_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<typename _Alloc>
bool vector<bool, _Alloc>::
_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<typename _Alloc>
void vector<bool, _Alloc>::
_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<value_type> __l,
const allocator_type& __a = allocator_type())
: _Base(__a)
{
_M_range_initialize(__l.begin(), __l.end(), random_access_iterator_tag());
}
//支持vector使用两个迭代器范围内的值初始化除了stl的迭代器也可以是数组地址
template<typename _InputIterator,
typename = std::_RequireInputIter<_InputIterator>>
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<typename _Tp, typename _Alloc>
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_storagethis->_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

View File

@ -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"<<std::endl;
}
};
```
由于它是空的所以这个sizeof是多少呢
```cpp
std::cout<<sizeof(Empty)<<std::endl; //1
```
结果是1**它是空的怎么不是0呢**
因为空类同样可以被实例化每个实例在内存中都有一个独一无二的地址为了达到这个目的编译器往往会给一个空类隐含的加一个字节这样空类在实例化后在内存得到了独一无二的地址所以上述大小为1.
根据上面的回答,估计大家或引出另一个问题:**为什么两个不同对象的地址应该不同?**
现在有下面这个例子:
```cpp
class Empty{
public:
void print() {
std::cout<<"I am Empty class"<<std::endl;
}
};
template < typename T >
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<<sizeof(notEbo)<<std::endl;
std::cout<<sizeof(ebo)<<std::endl;
```
输出:
```cpp
8
4
```
第一种会因为字节对齐将其原来只占1字节进行扩充到4的倍数最后就是8字节。
对比这两个发现,第二种通过继承方式来获得基类的功能,**并没有产生额外大小的优化称之为EBO(空基类优化)。**
接下来我们回到STL源码中看看其中的使用
## 3.STL中的EBO世界
不管是deque、rb_tree、list等容器都离不开内存管理在这几个容器中都包含了相应的内存管理并通过`_xx_impl`来继承下面这几个类:
```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>
```
那这和我们的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<typename _Tp>
class allocator: public __allocator_base<_Tp>
{
template<typename _Tp1>
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<<sizeof(std::allocator<int>)<<" "<<sizeof(std::allocator<int>::rebind<int>::other)<<endl;
cout<<sizeof(__gnu_cxx::bitmap_allocator<int>)<<" "<<sizeof(__gnu_cxx::bitmap_allocator<int>::rebind<int>::other)<<endl;
cout<<sizeof(__gnu_cxx::new_allocator<int>)<<" "<<sizeof(__gnu_cxx::new_allocator<int>::rebind<int>::other)<<endl;
cout<<sizeof(__gnu_cxx::__mt_alloc<int>)<<" "<<sizeof(__gnu_cxx::__mt_alloc<int>::rebind<int>::other)<<endl;
cout<<sizeof(__gnu_cxx::__pool_alloc<int>)<<" "<<sizeof(__gnu_cxx::__pool_alloc<int>::rebind<int>::other)<<endl;
cout<<sizeof(__gnu_cxx::malloc_allocator<int>)<<" "<<sizeof(__gnu_cxx::malloc_allocator<int>::rebind<int>::other)<<endl;
}
```
我们来测试这些sizeof是不是1经过测试输出如下
```cpp
1 1
1 1
1 1
1 1
1 1
1 1
```
说明内存管理的实现就是通过采用继承的方式,使用空基类优化,来达到尽量降低容器所占的大小。
## 4.利用EBO,手动实现一个简单的内存分配与释放
首先定义一个sizeof(class)=1的类同STL一样里面使用allocate与deallocate来进行内存管理。
```cpp
class MyAllocator {
public:
void *allocate(std::size_t size) {
return std::malloc(size);
}
void deallocate(void *ptr) {
std::free(ptr);
}
};
```
第一种方式的内存管理:嵌入一个内存管理类
```cpp
template<class T, class Allocator>
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<T *>(allocator_.allocate(capacity * sizeof(T))); // 分配内存
}
~MyContainerNotEBO() {
std::cout << "MyContainerNotEBO free malloc" << std::endl;
allocator_.deallocate(data_);
}
};
```
第二种方式:采用空基类优化,继承来获得内存管理功能
```cpp
template<class T, class Allocator>
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<T *>(this->allocate(capacity * sizeof(T)));
}
~MyContainerEBO() {
std::cout << "MyContainerEBO free malloc" << std::endl;
this->deallocate(data_);
}
};
```
开始测试:
```cpp
int main() {
MyContainerNotEBO<int, MyAllocator> notEbo = MyContainerNotEBO<int, MyAllocator>(0);
std::cout << "Using Not EBO Test sizeof is " << sizeof(notEbo) << std::endl;
MyContainerEBO<int, MyAllocator> ebo = MyContainerEBO<int, MyAllocator>(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的设计确实比嵌入设计好很多。至此本节学习完毕。