はじめに: 以前、Hou 氏の「STL ソース コード解析」を読んだことがありますが、それは何年も前のことであり、実際の作業で使用されている STL の実装を理解して、問題やクラッシュを確認する必要がある場合があります。そのため、STL のソース コードをもう一度確認する予定です。
概要: この記事では、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
さまざまなタイプの対応するノードを記述しますが、ここでは詳しく説明しません。
ツリー ノードは通常のノードであり、各ノードには、子ノードへの 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_;
}
++
これは、同様の操作 の簡単な外観です--
。バランス ツリーの次のノードは、順不同のトラバーサルの次のノードである必要があります。
- 現在のノードが親ノードの左側のノードである場合、次のノードは親ノードです。
- 現在のノードが親ノードの右側のノードである場合:
- その右の子ノードが空でない場合、次のノードは右のサブツリーの最小ノード (一番左の子ノード) です。
- 右の子ノードが空の場合、先祖ノードでその親ノードの右の子ノードであるノードを再帰的に見つけます。
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
両方をサポートするために、ツリー内の繰り返される要素と一意の要素に対して異なるインターフェイスが実装されます。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;
};
3map
と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)...);
}
4set
と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);}
};