C++ 소스 코드 분석 - set, multiset, map 및 multimap

  머리말 : Hou 씨의 "STL 소스 코드 분석"을 이전에 읽었지만 그것은 몇 년 전의 일입니다. 이제 때때로 직장에서 문제와 충돌을 확인하기 위해 실제 작업에서 사용되는 STL 구현을 이해해야 합니다. 따라서 다시 STL의 소스코드를 거쳐 갈 예정이다.
  Abstract : 이 기사에서는 mapllvm에서 libcxx를 구현하는 방법을 설명합니다.
  키워드 : map, rbtree, pair, set, multiset
  기타 : 참조 코드 LLVM-libcxx
  참고 : 코드를 참조할 때 llvm 구현은 gnu 및 msvc 구현과 다릅니다. 이 문서에서는 사용자가 데이터 구조에 이미 매우 익숙하다고 가정하고 해당 데이터 구조에 대한 세부 정보를 조사하지 않습니다.

  mapSTL의 연관 컨테이너로 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다양한 유형의 해당 노드를 설명하므로 여기에서는 자세히 설명하지 않습니다.
  트리 노드는 일반 노드이며 각 노드에는 자식 노드에 대한 두 개의 포인터, 부모 노드에 대한 포인터 및 현재 루트 노드의 색상을 나타내는 플래그가 포함됩니다. 여기서 값 필드와 인덱스의 포인터는 같은 클래스에 존재하는 것이 아니라 구분됩니다. 또한 __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_;
}

++다음은 유사한 작업   에 대한 간단한 보기입니다 --. 균형 트리의 다음 노드는 inorder traversal의 다음 노드여야 합니다.

  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. 트리에 동일한 키를 가진 노드가 있는지 확인합니다.
  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;
};

3 mapmultimap

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

4 setmultiset

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

Supongo que te gusta

Origin blog.csdn.net/GrayOnDream/article/details/130032446
Recomendado
Clasificación