C++ ソース コード分析 - set、multiset、map、および multimap

  はじめに: 以前、Hou 氏の「STL ソース コード解析」を読んだことがありますが、それは何年も前のことであり、実際の作業で使用されている STL の実装を理解して、問題やクラッシュを確認する必要がある場合があります。そのため、STL のソース コードをもう一度確認する予定です。
  概要: この記事では、mapllvm での libcxx の実装について説明します。
  キーワード: map, rbtree, pair, set,multiset
  その他: 参照コードLLVM-libcxx
  : コード参照時の llvm の実装は、gnu や msvc の実装とは異なります。この記事は、読者がデータ構造に精通していることを前提としており、対応するデータ構造の詳細については掘り下げません。

  mapこれは STL の連想コンテナです.vectorなどのシーケンス コンテナとは異なり、そのメモリ構造は論理的または物理的に直線的ではありません。mapキーと値のペアを介して要素を保存します。各キーは一意です。map赤黒ツリーで実装されているため、map検索効率が非常に高く、O(logn)検索、挿入、削除などの操作は、基本的に一定の時間複雑度内で実行されることが保証されます。

  mapの実現は、 STL にある赤黒ツリーによるものです__tree__treeしたがって、実装を理解すれば、基本的にマップの詳細の 90% を理解できます。

typedef __tree<__value_type, __vc, __allocator_type>   __base;

1本の木

1.1 ノード

  一般的な STL オブジェクトと同様に、ツリー ノードは__tree_node_typesさまざまなタイプの対応するノードを記述しますが、ここでは詳しく説明しません。
  ツリー ノードは通常のノードであり、各ノードには、子ノードへの 2 つのポインター、親ノードへのポインター、および現在のルート ノードの色を示すフラグが含まれます。ここでは、同じクラスに存在するのではなく、値フィールドとインデックスのポインターが区別されます。さらに、__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開始ノードと終了ノードの 2 つのノード、アロケータ、および現在のノード数とコンパレータがあります。ツリーの順序は、ツリー全体の一番左の子ノードが開始ノード__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. ツリー内に同じキーを持つノードがあるかどうかを調べます。
  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()));
}

2pair

  STL は、std::pair<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;
};

3mapmultimap

  mapとの実装はmultimap基本的に同じですが、要素の繰り返しを許可するかどうかが異なります。

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)...);
    }

4setmultiset

  setとの違いはmap、内部値が異なり、setキーのみが格納されることです。実装はmap赤黒木と同じですが、格納内容が異なります。setmultisetの違いは、mapと の違いと同じでmultimap、重複キーを許可するかどうかです。

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);}
};

おすすめ

転載: blog.csdn.net/GrayOnDream/article/details/130032446