STL源码之set与multiset
set
set的特性是,所有元素都会根据元素的键值自动被排序,set元素的键值就是实值,实值就是键值,set不允许两个元素有相同的键值。
set的元素值不可以通过迭代器来改变,因为set元素就是其键值,关系到set元素的排列规则;如果任何改变set元素值,会严重破坏set组织。
set是以rb-tree作为底层机制,又由于set所开放的各种操作接口,rb-tree也提供,所以几乎所有的set操作行为都只是调用rb-tree的操作行为。
set源码
1. set的构成
set的内部成员变量只有一个 _Rep_type _M_t; // Red-black tree representing set.
set的内部迭代器全部都是const_iterator,杜绝写入操作
set的成员变量类型中 typedef _Rb_tree<key_type, value_type, _Identity<value_type>,key_compare, _Key_alloc_type> _Rep_type可以看出,构造的rb-tree的value和key是同一个类型。
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;
__glibcxx_class_requires(_Key, _SGIAssignableConcept)
__glibcxx_class_requires4(_Compare, bool, _Key, _Key,
_BinaryFunctionConcept)
__glibcxx_class_requires2(_Key, _Alloc_value_type, _SameTypeConcept)
public:
// typedefs:
//@{
/// Public typedefs.
typedef _Key key_type;
typedef _Key value_type;
typedef _Compare key_compare;
typedef _Compare value_compare;
typedef _Alloc allocator_type;
//@}
private:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Key>::other _Key_alloc_type;
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.
typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;
public:
//@{
/// Iterator-related typedefs.
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 103. set::iterator is required to be modifiable,
// but this allows modification of keys.
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;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
//@}
// allocation/deallocation
/**
* @brief Default constructor creates no elements.
*/
set()
: _M_t() { }
。。。
}
2. 元素的插入操作
元素的插入操作insert实现如下,它内部调用的是_M_insert_unique,在C++进阶——STL源码之红黑树(_Rb_tree)一文中,我们已经分析过该函数,它对于新值的插入是不允许结点重复的,若重复,插入无效。
std::pair<iterator, bool>
insert(const value_type& __x)
{
std::pair<typename _Rep_type::iterator, bool> __p =
_M_t._M_insert_unique(__x);
return std::pair<iterator, bool>(__p.first, __p.second);
}
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>::
#if __cplusplus >= 201103L
_M_insert_unique(_Arg&& __v)
#else
_M_insert_unique(const _Val& __v)
#endif
{
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);
}
_M_get_insert_unique_pos的主要实现:插入新值,不允许重复,若重复插入无效。
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<_Base_ptr, _Base_ptr> _Res;
_Link_type __x = _M_begin();
_Link_type __y = _M_end();
bool __comp = true;
while (__x != 0)//从根节点开始,寻找合适的插入点
{
__y = __x;
__comp = _M_impl._M_key_compare(__k, _S_key(__x));
__x = __comp ? _S_left(__x) : _S_right(__x);
}
iterator __j = iterator(__y);//指向插入点的父结点
if (__comp)//comp为true,说明插在左侧
{
if (__j == begin())//插入结点的父结点为最左侧结点
return _Res(__x, __y);
else
--__j;
}
//新键值不与既有结点重复,于是执行安插
if (_M_impl._M_key_compare(_S_key(__j._M_node), __k))
return _Res(__x, __y);
return _Res(__j._M_node, 0);
}
multiset
multiset的特性以及用法和set完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制rb-tree的_M_insert_equal。
iterator
insert(const value_type& __x)
{ return _M_t._M_insert_equal(__x); }
调用的是rb-tree内部的方法,实现如下:
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
template<typename _Arg>
#endif
typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L
_M_insert_equal(_Arg&& __v)
#else
_M_insert_equal(const _Val& __v)
#endif
{
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,其中的实现是从根节点开始,往下寻找适当的插入点,区别于上述的新值的判断,这里是不做判断是否有重复的。
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);//x为插入点,y为插入点的父结点
}