C++源码剖析——set,multiset,map和multimap

  前言:之前看过侯老师的《STL源码剖析》但是那已经是多年以前的,现在工作中有时候查问题和崩溃都需要了解实际工作中使用到的STL的实现。因此计划把STL的源码再过一遍。
  摘要:本文描述了llvm中libcxx的map的实现。
  关键字map,rbtree,pair,set,multiset
  其他:参考代码LLVM-libcxx
  注意:参考代码时llvm的实现,与gnu和msvc的实现都有区别。文章假设你已经非常熟悉数据结构,并不会对对应的数据结构细节进行深究。

  map是STL中的关联容器,和vector等的序列容器不同,其内存结构无论是逻辑还是物理上都不是线性的。map通过键值对存储元素,每个key是唯一的。因为map是用红黑树实现的,因此map的查找效率是很高的,基本可以保证在O(logn)的时间复杂度内进行查找、插入,删除等操作。

  map的实现是通过红黑树实现的,在STL中就是__tree。因此我们理解了__tree的实现之后基本上能够明白map细节的90%。

typedef __tree<__value_type, __vc, __allocator_type>   __base;

1 树

1.1 节点

  和一般的STL对象一样,树节点通过__tree_node_types描述对应节点的各种类型,这里不详细描述。
  树节点就是常规的节点,每个节点包含两个子节点指针,一个父节点指针以及一个表示当前根节点的颜色的标志位。这里将值域和索引的指针进行了区分,而不是存在在同一个类里。另外__tree_node节点的析构函数是声明为delete的,其析构是通过__tree_node_destructor实现的,__tre_node_destructor的实现比较简单就是根据当前节点是否构造来选择释放内存还是析构对象。

template <class _Pointer>
class __tree_end_node{
public:
    typedef _Pointer pointer;
    pointer __left_;
};

template <class _VoidPtr>
class _LIBCPP_STANDALONE_DEBUG __tree_node_base : public __tree_node_base_types<_VoidPtr>::__end_node_type{
    typedef __tree_node_base_types<_VoidPtr> _NodeBaseTypes;
    pointer          __right_;
    __parent_pointer __parent_;
    bool __is_black_;
};

template <class _Tp, class _VoidPtr>
class _LIBCPP_STANDALONE_DEBUG __tree_node : public __tree_node_base<_VoidPtr>{
public:
    typedef _Tp __node_value_type;

    __node_value_type __value_;

private:
  ~__tree_node() = delete;
  __tree_node(__tree_node const&) = delete;
  __tree_node& operator=(__tree_node const&) = delete;
};

//树节点的析构器
template <class _Allocator>
class __tree_node_destructor{
public:
    bool __value_constructed;
    __tree_node_destructor(const __tree_node_destructor &) = default;
    __tree_node_destructor& operator=(const __tree_node_destructor&) = delete;
    explicit __tree_node_destructor(allocator_type& __na, bool __val = false) _NOEXCEPT : __na_(__na), __value_constructed(__val){}

    void operator()(pointer __p) _NOEXCEPT{
        if (__value_constructed)
            __alloc_traits::destroy(__na_, _NodeTypes::__get_ptr(__p->__value_));
        if (__p)
            __alloc_traits::deallocate(__na_, __p, 1);
    }
};

1.2 树迭代器

  树的迭代器就是一个节点的指针,节点操作的方式就是红黑树的基本操作方式。

//定义在__tree_node_types中
typedef __conditional_t< is_pointer<__node_pointer>::value, typename __base::__end_node_pointer, __node_pointer>
      __iter_pointer;

template <class _Tp, class _NodePtr, class _DiffType>
class _LIBCPP_TEMPLATE_VIS __tree_iterator{
    typedef typename _NodeTypes::__iter_pointer             __iter_pointer;
    __iter_pointer __ptr_;
}

  这里简单看下++的操作,--类似。平衡树的下一个节点应该是其中序遍历的下一个节点:

  1. 如果当前节点为父节点的左节点,则下一个节点为父节点;
  2. 如果当前节点为父节点的右节点:
    1. 且其右子节点不为空,则下一个节点为右子树的最小节点(最左子节点);
    2. 若右子节点为空,则向上递归找到祖先节点中是其父节点的右子节点的节点;
template <class _EndNodePtr, class _NodePtr>
inline _LIBCPP_INLINE_VISIBILITY _EndNodePtr __tree_next_iter(_NodePtr __x) _NOEXCEPT{
    _LIBCPP_ASSERT(__x != nullptr, "node shouldn't be null");
    if (__x->__right_ != nullptr)       //情况2
        return static_cast<_EndNodePtr>(_VSTD::__tree_min(__x->__right_));
    while (!_VSTD::__tree_is_left_child(__x))//情况2和情况3
        __x = __x->__parent_unsafe();
    return static_cast<_EndNodePtr>(__x->__parent_);
}

1.3 树

  STL中树的实现是__tree,因为是有序的容器因此用户需要提供一个比较器(默认就是==)。__tree中有两个节点,开始节点和结束节点,一个分配器,和当前数的节点数以及比较器。树的顺序是按照中序排列的,开始节点即整棵树的最左子节点就是__begin_node_,根节点就是__pair1_.first()->left,尾结点即最右子节点就是__pair1_.first()

template <class _Tp, class _Compare, class _Allocator>
class __tree{
    __iter_pointer                                     __begin_node_;
    __compressed_pair<__end_node_t, __node_allocator>  __pair1_;
    __compressed_pair<size_type, value_compare>        __pair3_;
};

  树为空时__begin_node == __end_node
  树中针对重复元素和唯一元素实现了不同的接口,这样是为了同时支持mapmultmap

iterator __insert_unique(const_iterator __p, _Vp&& __v) {
    return __emplace_hint_unique(__p, _VSTD::forward<_Vp>(__v));
}

iterator __insert_multi(__container_value_type&& __v) {
    return __emplace_multi(_VSTD::move(__v));
}

__emplace_hint_unique
  __emplace_hint_unique用于向当前树中插入一个节点,其基本步骤是:

  1. 构造节点;
  2. 在树中查找是否存在相同key的节点;
  3. 将节点插入树中。
template <class _Tp, class _Compare, class _Allocator>
template <class... _Args>
typename __tree<_Tp, _Compare, _Allocator>::iterator
__tree<_Tp, _Compare, _Allocator>::__emplace_hint_unique_impl(const_iterator __p, _Args&&... __args){
    __node_holder __h = __construct_node(_VSTD::forward<_Args>(__args)...);
    __parent_pointer __parent;
    __node_base_pointer __dummy;
    __node_base_pointer& __child = __find_equal(__p, __parent, __dummy, __h->__value_);
    __node_pointer __r = static_cast<__node_pointer>(__child);
    if (__child == nullptr)
    {
        __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
        __r = __h.release();
    }
    return iterator(__r);
}

  下面我们一步一步看,构造节点就是调用construct_node实现的,在树中查找节点是通过__find_equal实现的。__find_equal的实现比较简单就是通过非递归中序遍历的过程。其中value_comp是自定义的比较函数对象,默认是lesser。需要注意的是

template <class _Tp, class _Compare, class _Allocator>
template <class _Key>
typename __tree<_Tp, _Compare, _Allocator>::__node_base_pointer& __tree<_Tp, _Compare, _Allocator>::__find_equal(__parent_pointer& __parent, const _Key& __v){
    __node_pointer __nd = __root();
    __node_base_pointer* __nd_ptr = __root_ptr();
    if (__nd != nullptr){
        while (true){
            if (value_comp()(__v, __nd->__value_)){
                if (__nd->__left_ != nullptr) {
                    __nd_ptr = _VSTD::addressof(__nd->__left_);
                    __nd = static_cast<__node_pointer>(__nd->__left_);
                } else {
                    __parent = static_cast<__parent_pointer>(__nd);
                    return __parent->__left_;
                }
            }
            else if (value_comp()(__nd->__value_, __v)){
                if (__nd->__right_ != nullptr) {
                    __nd_ptr = _VSTD::addressof(__nd->__right_);
                    __nd = static_cast<__node_pointer>(__nd->__right_);
                } else {
                    __parent = static_cast<__parent_pointer>(__nd);
                    return __nd->__right_;
                }
            }
            else{
                __parent = static_cast<__parent_pointer>(__nd);
                return *__nd_ptr;
            }
        }
    }
    __parent = static_cast<__parent_pointer>(__end_node());
    return __parent->__left_;
}

  找到要插入的位置,我们看下是如何插入的。基本过程是先插入节点到预定的位置,然后检查节点与父节点的颜色是否正确,如果不正确就会对树进行调整。插入是通过__insert_node_at实现的。

template <class _Tp, class _Compare, class _Allocator>
void __tree<_Tp, _Compare, _Allocator>::__insert_node_at( __parent_pointer __parent, __node_base_pointer& __child, __node_base_pointer __new_node) _NOEXCEPT{
    __new_node->__left_   = nullptr;
    __new_node->__right_  = nullptr;
    __new_node->__parent_ = __parent;
    // __new_node->__is_black_ is initialized in __tree_balance_after_insert
    __child = __new_node;
    if (__begin_node()->__left_ != nullptr)
        __begin_node() = static_cast<__iter_pointer>(__begin_node()->__left_);
    _VSTD::__tree_balance_after_insert(__end_node()->__left_, __child);
    ++size();
}
template <class _NodePtr>
_LIBCPP_HIDE_FROM_ABI void
__tree_balance_after_insert(_NodePtr __root, _NodePtr __x) _NOEXCEPT{
    _LIBCPP_ASSERT(__root != nullptr, "Root of the tree shouldn't be null");
    _LIBCPP_ASSERT(__x != nullptr, "Can't attach null node to a leaf");
    __x->__is_black_ = __x == __root;
    while (__x != __root && !__x->__parent_unsafe()->__is_black_){
        // __x->__parent_ != __root because __x->__parent_->__is_black == false
        if (_VSTD::__tree_is_left_child(__x->__parent_unsafe())){
            _NodePtr __y = __x->__parent_unsafe()->__parent_unsafe()->__right_;
            if (__y != nullptr && !__y->__is_black_){
                //新插入的节点改变了整棵树的颜色数量,需要对颜色进行改变
                __x = __x->__parent_unsafe();
                __x->__is_black_ = true;
                __x = __x->__parent_unsafe();
                __x->__is_black_ = __x == __root;
                __y->__is_black_ = true;
            }else{
                //如果插入的位置是左子节点的右节点需要先左旋转,再右旋转,否则只需要右旋转
                if (!_VSTD::__tree_is_left_child(__x)){
                    __x = __x->__parent_unsafe();
                    _VSTD::__tree_left_rotate(__x);
                }
                __x = __x->__parent_unsafe();
                __x->__is_black_ = true;
                __x = __x->__parent_unsafe();
                __x->__is_black_ = false;
                _VSTD::__tree_right_rotate(__x);
                break;
            }
        }
        else{
            _NodePtr __y = __x->__parent_unsafe()->__parent_->__left_;
            if (__y != nullptr && !__y->__is_black_){
                //新插入的节点改变了整棵树的颜色数量,需要对颜色进行改变
                __x = __x->__parent_unsafe();
                __x->__is_black_ = true;
                __x = __x->__parent_unsafe();
                __x->__is_black_ = __x == __root;
                __y->__is_black_ = true;
            }else{
                //如果插入的位置是右子节点的左节点需要先右旋转,再左旋转,否则只需要左旋转
                if (_VSTD::__tree_is_left_child(__x)){
                    __x = __x->__parent_unsafe();
                    _VSTD::__tree_right_rotate(__x);
                }
                __x = __x->__parent_unsafe();
                __x->__is_black_ = true;
                __x = __x->__parent_unsafe();
                __x->__is_black_ = false;
                _VSTD::__tree_left_rotate(__x);
                break;
            }
        }
    }
}

__emplace_hint_multi
  __emplace_hint_multi__emplace_hint_unique的区别就是搜索插入节点的方式。

template <class _Tp, class _Compare, class _Allocator>
template <class... _Args>
typename __tree<_Tp, _Compare, _Allocator>::iterator __tree<_Tp, _Compare, _Allocator>::__emplace_hint_multi(const_iterator __p, _Args&&... __args){
    __node_holder __h = __construct_node(_VSTD::forward<_Args>(__args)...);
    __parent_pointer __parent;
    __node_base_pointer& __child = __find_leaf(__p, __parent, _NodeTypes::__get_key(__h->__value_));
    __insert_node_at(__parent, __child, static_cast<__node_base_pointer>(__h.get()));
    return iterator(static_cast<__node_pointer>(__h.release()));
}

2 pair

  STL中通过std::pair<key, value>来表示key——value键值对。实现比较简单,不同于compressed_pair

template <class _T1, class _T2>
struct _LIBCPP_TEMPLATE_VIS pair
#if defined(_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR)
: private __non_trivially_copyable_base<_T1, _T2>
#endif
{
    _T1 first;
    _T2 second;
};

3 mapmultimap

  mapmultimap的实现基本差不多,区别是是否允许重复元素。

template <class _Key, class _Tp, class _Compare = less<_Key>, class _Allocator = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS map{
public:
    typedef _Key                                     key_type;
    typedef _Tp                                      mapped_type;
    typedef pair<const key_type, mapped_type>        value_type;
private:
    __base __tree_;
};

template <class _Key, class _Tp, class _Compare, class _Allocator>
_Tp& map<_Key, _Tp, _Compare, _Allocator>::operator[](const key_type& __k){
    return __tree_.__emplace_unique_key_args(__k,
        _VSTD::piecewise_construct,
        _VSTD::forward_as_tuple(__k),
        _VSTD::forward_as_tuple()).first->__get_value().second;
}
   
template <class _Key, class _Tp, class _Compare = less<_Key>, class _Allocator = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS multimap{
public:
    typedef _Key                                     key_type;
    typedef _Tp                                      mapped_type;
    typedef pair<const key_type, mapped_type>        value_type;
private:
    __base __tree_;
};

    template <class ..._Args>
    _LIBCPP_INLINE_VISIBILITY
    iterator emplace(_Args&& ...__args) {
        return __tree_.__emplace_multi(_VSTD::forward<_Args>(__args)...);
    }

4 setmultiset

  setmap的区别就是其内部的值不同,set仅仅存储key。其实现和map相同都是红黑树,只是存储的内容不同。setmultiset的区别与mapmultimap的区别相同,都是是否允许出现重复key。

template <class _Key, class _Compare = less<_Key>, class _Allocator = allocator<_Key> >
class _LIBCPP_TEMPLATE_VIS set{
public:
    typedef _Key                                     key_type;
    typedef key_type                                 value_type;
    static_assert((is_same<typename allocator_type::value_type, value_type>::value),
                  "Allocator::value_type must be same type as value_type");

private:
    typedef __tree<value_type, value_compare, allocator_type> __base;
    __base __tree_;
public:
    iterator insert(const_iterator __p, const value_type& __v)
        {return __tree_.__insert_unique(__p, __v);}
}
template <class _Key, class _Compare = less<_Key>,class _Allocator = allocator<_Key> >
class _LIBCPP_TEMPLATE_VIS multiset{
public:
    typedef _Key                                     key_type;
    typedef key_type                                 value_type;
private:
    typedef __tree<value_type, value_compare, allocator_type> __base;
    __base __tree_;
public:
    iterator insert(const value_type& __v)
        {return __tree_.__insert_multi(__v);}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值