Foreword : I have read Mr. Hou's "STL Source Code Analysis" before, but that was many years ago. Now, sometimes you need to understand the implementation of STL used in actual work to check problems and crashes at work. Therefore, it is planned to go through the source code of STL again.
Abstract : This article describes list
the implementation of libcxx in llvm.
Keywords : list
Other : Reference code LLVM-libcxx
Note : The implementation of llvm when referring to the code is different from the implementation of gnu and msvc.
list
It is a doubly linked list provided by STL, which can operate elements at the head and tail.
1 node
As forward_list
with , list
a distinction is made between indexes and nodes. Indexes only contain pointers to the corresponding access nodes, while nodes contain both linked pointers and data members.
template <class _Tp, class _VoidPtr>
struct __list_node_base{
typedef __list_node_pointer_traits<_Tp, _VoidPtr> _NodeTraits;
typedef typename _NodeTraits::__link_pointer __link_pointer;
__link_pointer __prev_; //前向指针
__link_pointer __next_; //后向指针
};
template <class _Tp, class _VoidPtr>
struct _LIBCPP_STANDALONE_DEBUG __list_node : public __list_node_base<_Tp, _VoidPtr>{
_Tp __value_;
};
2 iterators
list
The iterator is a list_node
pointer, and its self-increment and self-decrement operations are relatively simple to access the forward and backward connection pointers of the node.
template <class _Tp, class _VoidPtr>
class _LIBCPP_TEMPLATE_VIS __list_iterator{
typedef __list_node_pointer_traits<_Tp, _VoidPtr> _NodeTraits;
typedef typename _NodeTraits::__link_pointer __link_pointer;
__link_pointer __ptr_;
__list_iterator& operator++(){
_LIBCPP_DEBUG_ASSERT(__get_const_db()->__dereferenceable(this),
"Attempted to increment a non-incrementable list::iterator");
__ptr_ = __ptr_->__next_;
return *this;
}
__list_iterator& operator--(){
_LIBCPP_DEBUG_ASSERT(__get_const_db()->__decrementable(this),
"Attempted to decrement a non-decrementable list::iterator");
__ptr_ = __ptr_->__prev_;
return *this;
}
};
3 list
achieved
list
The memory layout of is __list_impl
described by , and the members __end_
as index nodes describe the head and tail of the current linked list. __end_
The next
and prev
two pointers point to the head and tail nodes of the linked list respectively, __size_alloc_
and the length of the current linked list is saved.
template <class _Tp, class _Alloc>
class __list_imp{
__node_base __end_;
__compressed_pair<size_type, __node_allocator> __size_alloc_;
iterator begin() _NOEXCEPT{
return iterator(__end_.__next_, this);
}
iterator end() _NOEXCEPT{
return iterator(__end_as_link(), this);
}
};
The operation of the node is a typical double-linked list operation, there is nothing to say. clear
The operation for-loop
is realized by traversing each node and destroying the corresponding node object and memory.
template <class _Tp, class _Alloc>
void __list_imp<_Tp, _Alloc>::clear() _NOEXCEPT{
if (!empty()){
__node_allocator& __na = __node_alloc();
__link_pointer __f = __end_.__next_;
__link_pointer __l = __end_as_link();
__unlink_nodes(__f, __l->__prev_);
__sz() = 0;
while (__f != __l){
__node_pointer __np = __f->__as_node();
__f = __f->__next_;
__node_alloc_traits::destroy(__na, _VSTD::addressof(__np->__value_));
__node_alloc_traits::deallocate(__na, __np, 1);
}
std::__debug_db_invalidate_all(this);
}
}
list
It __list_impl
encapsulates common linked list operations on the basis of .
template <class _Tp, class _Alloc /*= allocator<_Tp>*/>
class _LIBCPP_TEMPLATE_VIS list : private __list_imp<_Tp, _Alloc>{
};
Let's take a brief look push_back
at pop_back
how it is implemented (in fact, it is a simple insertion of linked list nodes).
push_back
First create memory to construct the input object on the corresponding memory, and then insert the node to the end. Node insertion is also relatively simple, which is the general double-linked list node operation, which will not be described in detail.
template <class _Tp, class _Alloc>
void list<_Tp, _Alloc>::push_back(const value_type& __x){
__node_allocator& __na = base::__node_alloc();
__hold_pointer __hold = __allocate_node(__na);
__node_alloc_traits::construct(__na, _VSTD::addressof(__hold->__value_), __x);
__link_nodes_at_back(__hold.get()->__as_link(), __hold.get()->__as_link());
++base::__sz();
__hold.release();
}
template <class _Tp, class _Alloc>
inline void list<_Tp, _Alloc>::__link_nodes_at_back(__link_pointer __f, __link_pointer __l){
__l->__next_ = base::__end_as_link();
__f->__prev_ = base::__end_.__prev_;
__f->__prev_->__next_ = __f;
base::__end_.__prev_ = __l;
}
pop_front
The realization of is also relatively simple and intuitive, and will not be described in detail.
template <class _Tp, class _Alloc>
void list<_Tp, _Alloc>::pop_front(){
_LIBCPP_ASSERT(!empty(), "list::pop_front() called with empty list");
__node_allocator& __na = base::__node_alloc();
__link_pointer __n = base::__end_.__next_;
base::__unlink_nodes(__n, __n);
--base::__sz();
__node_pointer __np = __n->__as_node();
__node_alloc_traits::destroy(__na, _VSTD::addressof(__np->__value_));
__node_alloc_traits::deallocate(__na, __np, 1);
}
template <class _Tp, class _Alloc>
inline void __list_imp<_Tp, _Alloc>::__unlink_nodes(__link_pointer __f, __link_pointer __l)_NOEXCEPT{
__f->__prev_->__next_ = __l->__next_;
__l->__next_->__prev_ = __f->__prev_;
}