머리말 : Hou 씨의 "STL 소스 코드 분석"을 이전에 읽었지만 그것은 몇 년 전의 일입니다. 이제 때때로 직장에서 문제와 충돌을 확인하기 위해 실제 작업에서 사용되는 STL 구현을 이해해야 합니다. 따라서 다시 STL의 소스코드를 거쳐 갈 예정이다.
Abstract : 이 기사에서는 map
llvm에서 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
다양한 유형의 해당 노드를 설명하므로 여기에서는 자세히 설명하지 않습니다.
트리 노드는 일반 노드이며 각 노드에는 자식 노드에 대한 두 개의 포인터, 부모 노드에 대한 포인터 및 현재 루트 노드의 색상을 나타내는 플래그가 포함됩니다. 여기서 값 필드와 인덱스의 포인터는 같은 클래스에 존재하는 것이 아니라 구분됩니다. 또한 __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의 다음 노드여야 합니다.
- 현재 노드가 부모 노드의 왼쪽 노드이면 다음 노드는 부모 노드입니다.
- 현재 노드가 상위 노드의 오른쪽 노드인 경우:
- 오른쪽 자식 노드가 비어 있지 않으면 다음 노드는 오른쪽 하위 트리의 가장 작은 노드(가장 왼쪽 자식 노드)입니다.
- 오른쪽 자식 노드가 비어 있으면 조상 노드에서 부모 노드의 오른쪽 자식 노드인 노드를 재귀적으로 찾습니다.
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
지원하기 위해 트리의 반복 요소 및 고유 요소에 대해 서로 다른 인터페이스가 구현됩니다 .map
multmap
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
는 현재 트리에 노드를 삽입하는 데 사용되며 기본 단계는 다음과 같습니다.
- 구성 노드;
- 트리에 동일한 키를 가진 노드가 있는지 확인합니다.
- 트리에 노드를 삽입합니다.
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 map
과multimap
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 set
와multiset
set
와 의 차이점은 map
내부 값이 다르고 set
키만 저장된다는 것입니다. 그 구현은 map
레드-블랙 트리와 동일하지만 저장된 내용이 다릅니다. set
와 multiset
의 차이는 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);}
};