C++ STL 容器设计与实现分析 -- list
3、list
list 是双向链表实现。双向链表结点在堆上动态申请,插入和删除操作不存在移动元素的操作。
3.1、_List_node
list 中每个结点都用 _List_node 表示,继承于 _List_node_base。_List_node 成员 _M_storage,用于保存数据。
/// stl_list.h
/// An actual node in the %list.
template<typename _Tp>
struct _List_node : public __detail::_List_node_base
{
#if __cplusplus >= 201103L
__gnu_cxx::__aligned_membuf<_Tp> _M_storage;
_Tp* _M_valptr() { return _M_storage._M_ptr(); }
_Tp const* _M_valptr() const { return _M_storage._M_ptr(); }
#else
// ...
#endif
};
_List_node_base 只有两个指针成员 next 和 prev,分别指向 list 中前一个和后一个成员。_List_node_base 定义了一组函数接口,用于将本 node 链接或者移除 list。
/// stl_list.h
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
static void
swap(_List_node_base& __x, _List_node_base& __y) _GLIBCXX_USE_NOEXCEPT;
void
_M_transfer(_List_node_base* const __first,
_List_node_base* const __last) _GLIBCXX_USE_NOEXCEPT;
void
_M_reverse() _GLIBCXX_USE_NOEXCEPT;
void
_M_hook(_List_node_base* const __position) _GLIBCXX_USE_NOEXCEPT;
void
_M_unhook() _GLIBCXX_USE_NOEXCEPT;
};
接下来分析 _List_node_base 定义的函数接口。首先是 swap 函数。用于将两个 x 和 y 交换。
/// c++98/list.cc
void
_List_node_base::swap(_List_node_base& __x,
_List_node_base& __y) _GLIBCXX_USE_NOEXCEPT
{
if ( __x._M_next != &__x ) // x 不为空
{
if ( __y._M_next != &__y ) // y 不为空
{ // x 和 y 都不为空,交换 x 和 y 的内容
// Both __x and __y are not empty.
std::swap(__x._M_next,__y._M_next);
std::swap(__x._M_prev,__y._M_prev);
__x._M_next->_M_prev = __x._M_prev->_M_next = &__x;
__y._M_next->_M_prev = __y._M_prev->_M_next = &__y;
}
else
{ // x 不为空,y 为空
// __x is not empty, __y is empty.
__y._M_next = __x._M_next;
__y._M_prev = __x._M_prev;
__y._M_next->_M_prev = __y._M_prev->_M_next = &__y;
__x._M_next = __x._M_prev = &__x;
}
}
else if ( __y._M_next != &__y )
{ // x 为 空,y 不为空
// __x is empty, __y is not empty.
__x._M_next = __y._M_next;
__x._M_prev = __y._M_prev;
__x._M_next->_M_prev = __x._M_prev->_M_next = &__x;
__y._M_next = __y._M_prev = &__y;
}
}
_M_transfer() 函数 [first, last) 的元素全部转移到 *this 所在的 list。
/// c++98/list.cc
void
_List_node_base::
_M_transfer(_List_node_base * const __first,
_List_node_base * const __last) _GLIBCXX_USE_NOEXCEPT
{
__glibcxx_assert(__first != __last);
if (this != __last)
{
// Remove [first, last) from its old position.
__last->_M_prev->_M_next = this;
__first->_M_prev->_M_next = __last;
this->_M_prev->_M_next = __first;
// Splice [first, last) into its new position.
_List_node_base* const __tmp = this->_M_prev;
this->_M_prev = __last->_M_prev;
__last->_M_prev = __first->_M_prev;
__first->_M_prev = __tmp;
}
}
_M_reverse() 函数用于将所有 node 的 next 和 prev 交换,改变 list 的指向。
/// c++98/list.cc
void
_List_node_base::_M_reverse() _GLIBCXX_USE_NOEXCEPT
{
_List_node_base* __tmp = this;
do
{
std::swap(__tmp->_M_next, __tmp->_M_prev);
// Old next node is now prev.
__tmp = __tmp->_M_prev;
}
while (__tmp != this);
}
_M_hook() 和 _M_unhook() 两个函数用户将 *this 插入和删除。
/// c++98/list.cc
void
_List_node_base::
_M_hook(_List_node_base* const __position) _GLIBCXX_USE_NOEXCEPT
{
this->_M_next = __position;
this->_M_prev = __position->_M_prev;
__position->_M_prev->_M_next = this;
__position->_M_prev = this;
}
void
_List_node_base::_M_unhook() _GLIBCXX_USE_NOEXCEPT
{
_List_node_base* const __next_node = this->_M_next;
_List_node_base* const __prev_node = this->_M_prev;
__prev_node->_M_next = __next_node;
__next_node->_M_prev = __prev_node;
}
3.2、_List_iterator
list 的迭代器区分 const 和 非 const 类型。两者保存的都是 _List_node_base 类型的指针,指向对应的 node。当向 list 插入和删除元素后,其他迭代器不会失效。
值得注意的是,list 的迭代器数据成员 _M_node 是 public 成员,可以直接访问。
/// stl_list.h
template<typename _Tp>
struct _List_iterator
{
typedef _List_iterator<_Tp> _Self;
typedef _List_node<_Tp> _Node;
typedef ptrdiff_t difference_type;
typedef std::bidirectional_iterator_tag iterator_category;
typedef _Tp value_type;
typedef _Tp* pointer;
typedef _Tp& reference;
// ...
// The only member points to the %list element.
__detail::_List_node_base* _M_node;
};
template<typename _Tp>
struct _List_const_iterator
{
typedef _List_const_iterator<_Tp> _Self;
typedef const _List_node<_Tp> _Node;
typedef _List_iterator<_Tp> iterator;
typedef ptrdiff_t difference_type;
typedef std::bidirectional_iterator_tag iterator_category;
typedef _Tp value_type;
typedef const _Tp* pointer;
typedef const _Tp& reference;
// ...
// The only member points to the %list element.
const __detail::_List_node_base* _M_node;
};
3.3、_List_node_header
链表头节点使用 _List_node_header 表示。头结点保存有 size,表示 list 中有多少个元素。构造时,头节点 next 和 prev 都指向头节点自己。
/// stl_list.h
struct _List_node_header : public _List_node_base
{
#if _GLIBCXX_USE_CXX11_ABI
std::size_t _M_size;
#endif
_List_node_header() _GLIBCXX_NOEXCEPT
{ _M_init(); }
#if __cplusplus >= 201103L
_List_node_header(_List_node_header&& __x) noexcept
: _List_node_base{ __x._M_next, __x._M_prev }
# if _GLIBCXX_USE_CXX11_ABI
, _M_size(__x._M_size)
# endif
{
if (__x._M_base()->_M_next == __x._M_base())
this->_M_next = this->_M_prev = this;
else
{
this->_M_next->_M_prev = this->_M_prev->_M_next = this->_M_base();
__x._M_init();
}
}
void
_M_move_nodes(_List_node_header&& __x)
{
_List_node_base* const __xnode = __x._M_base();
if (__xnode->_M_next == __xnode)
_M_init();
else
{
_List_node_base* const __node = this->_M_base();
__node->_M_next = __xnode->_M_next;
__node->_M_prev = __xnode->_M_prev;
__node->_M_next->_M_prev = __node->_M_prev->_M_next = __node;
# if _GLIBCXX_USE_CXX11_ABI
_M_size = __x._M_size;
# endif
__x._M_init();
}
}
#endif
void
_M_init() _GLIBCXX_NOEXCEPT
{
this->_M_next = this->_M_prev = this;
#if _GLIBCXX_USE_CXX11_ABI
this->_M_size = 0;
#endif
}
private:
_List_node_base* _M_base() { return this; }
};
3.4、std::list
首先分析 _List_base。_List_base 是 list 的基类,定义了链表基本的操作。
首先 _List_base 内部定义了一个数据据结构 _List_impl,继承于 _Node_alloc_type,有一个 _List_node_header 数据成员,也就是 _List_impl 保存有链表头节点。
然后 _List_base 定义了一个 _List_impl 类型的成员。整个结构比较清晰,_List_base 定义了链表的结构,其头节点保存在 _M_impl 数据成员中。
/// stl_list.h
template<typename _Tp, typename _Alloc>
class _List_base
{
protected:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tp_alloc_traits;
typedef typename _Tp_alloc_traits::template
rebind<_List_node<_Tp> >::other _Node_alloc_type;
typedef __gnu_cxx::__alloc_traits<_Node_alloc_type> _Node_alloc_traits;
struct _List_impl
: public _Node_alloc_type
{
__detail::_List_node_header _M_node;
// ...
};
_List_impl _M_impl;
// ...
void
_M_init() _GLIBCXX_NOEXCEPT
{ this->_M_impl._M_node._M_init(); }
};
list 继承于 _List_base,没有其他的数据成员。
/// stl_list.h
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class list : protected _List_base<_Tp, _Alloc>
{
typedef _List_base<_Tp, _Alloc> _Base;
typedef typename _Base::_Tp_alloc_type _Tp_alloc_type;
typedef typename _Base::_Tp_alloc_traits _Tp_alloc_traits;
typedef typename _Base::_Node_alloc_type _Node_alloc_type;
typedef typename _Base::_Node_alloc_traits _Node_alloc_traits;
public:
typedef _Tp value_type;
typedef typename _Tp_alloc_traits::pointer pointer;
typedef typename _Tp_alloc_traits::const_pointer const_pointer;
typedef typename _Tp_alloc_traits::reference reference;
typedef typename _Tp_alloc_traits::const_reference const_reference;
typedef _List_iterator<_Tp> iterator;
typedef _List_const_iterator<_Tp> const_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Alloc allocator_type;
protected:
// Note that pointers-to-_Node's can be ctor-converted to
// iterator types.
typedef _List_node<_Tp> _Node;
using _Base::_M_impl;
using _Base::_M_put_node;
using _Base::_M_get_node;
using _Base::_M_get_Node_allocator;
// ...
begin() 返回头节点 next 指向的结点,而 end() 返回头结点。头节点 next 指向的是 list 第一个结点,而 prev 指向的是 list 最后一个结点。
/// stl_list.h
_GLIBCXX_NODISCARD
iterator
begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_node._M_next); }
iterator
end() _GLIBCXX_NOEXCEPT
{ return iterator(&this->_M_impl._M_node); }
当向 list 插入一个元素时,首先会调用 _M_create_node() 函数申请和构造一个结点。_M_get_node() 在堆上申请一块内存,然后在这块内存上构造一个结点。
/// stl_list.h
template<typename... _Args>
_Node*
_M_create_node(_Args&&... __args)
{
auto __p = this->_M_get_node();
auto& __alloc = _M_get_Node_allocator();
__allocated_ptr<_Node_alloc_type> __guard{__alloc, __p};
_Node_alloc_traits::construct(__alloc, __p->_M_valptr(),
std::forward<_Args>(__args)...);
__guard = nullptr;
return __p;
}
插入和删除结点分别调用 _M_insert() 和 _M_erase() 函数。
_M_insert() 函数首先调用 _M_create_node() 构造一个结点,然后调用 _M_hook() 函数将其插入到 position 结点前面。
_M_erase() 函数和 _M_insert() 函数类似,执行相反的操作删除一个结点。首先调用 _M_unhook() 函数将结点从 list 删除,然后调用 destroy 函数析构内存,最后调用 _M_put_node() 释放内存。
/// stl_list.h
template<typename... _Args>
void
_M_insert(iterator __position, _Args&&... __args)
{
_Node* __tmp = _M_create_node(std::forward<_Args>(__args)...);
__tmp->_M_hook(__position._M_node);
this->_M_inc_size(1);
}
void
_M_erase(iterator __position) _GLIBCXX_NOEXCEPT
{
this->_M_dec_size(1);
__position._M_node->_M_unhook();
_Node* __n = static_cast<_Node*>(__position._M_node);
_Node_alloc_traits::destroy(_M_get_Node_allocator(), __n->_M_valptr());
_M_put_node(__n);
}
merge() 用于合并两 list 并且保证合并后保持递增顺序。
/// list.tcc
template<typename _Tp, typename _Alloc>
void
list<_Tp, _Alloc>::
#if __cplusplus >= 201103L
merge(list&& __x)
#else
merge(list& __x)
#endif
{
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 300. list::merge() specification incomplete
if (this != std::__addressof(__x))
{
_M_check_equal_allocators(__x);
iterator __first1 = begin();
iterator __last1 = end();
iterator __first2 = __x.begin();
iterator __last2 = __x.end();
const _Finalize_merge __fin(*this, __x, __first2);
while (__first1 != __last1 && __first2 != __last2)
if (*__first2 < *__first1) // first2 小于 first1
{
iterator __next = __first2; // 将 frist2 转移到 first1 前面
_M_transfer(__first1, __first2, ++__next);
__first2 = __next;
}
else
++__first1;
if (__first2 != __last2)
{ // 将 [first2, last2) 元素转移到 last1 前面
_M_transfer(__last1, __first2, __last2);
__first2 = __last2;
}
}
}
merge() 函数主要调用 _M_transfer() 函数,其直接调用 node 的 _M_transfer() 函数。
/// stl_list.h
void
_M_transfer(iterator __position, iterator __first, iterator __last)
{ __position._M_node->_M_transfer(__first._M_node, __last._M_node); }
sort() 函数将 list 排序,需要使用 _Scratch_list 数据结构。
首先分析 _Scratch_list 数据结构。_Scratch_list 用于部分排序的链表。
/// stl_list.h
// Used by list::sort to hold nodes being sorted.
struct _Scratch_list : _List_node_base
{
_Scratch_list() { _M_next = _M_prev = this; }
bool empty() const { return _M_next == this; }
void swap(_List_node_base& __l) { _List_node_base::swap(*this, __l); }
template<typename _Iter, typename _Cmp>
struct _Ptr_cmp
{
_Cmp _M_cmp;
bool
operator()(__detail::_List_node_base* __lhs,
__detail::_List_node_base* __rhs) /* not const */
{ return _M_cmp(*_Iter(__lhs), *_Iter(__rhs)); }
};
template<typename _Iter>
struct _Ptr_cmp<_Iter, void>
{
bool
operator()(__detail::_List_node_base* __lhs,
__detail::_List_node_base* __rhs) const
{ return *_Iter(__lhs) < *_Iter(__rhs); }
};
// Merge nodes from __x into *this. Both lists must be sorted wrt _Cmp.
template<typename _Cmp>
void
merge(_List_node_base& __x, _Cmp __comp)
{ // 归并排序
_List_node_base* __first1 = _M_next;
_List_node_base* const __last1 = this;
_List_node_base* __first2 = __x._M_next;
_List_node_base* const __last2 = std::__addressof(__x);
while (__first1 != __last1 && __first2 != __last2)
{
if (__comp(__first2, __first1)) // first2 小于 first1
{
_List_node_base* __next = __first2->_M_next;
__first1->_M_transfer(__first2, __next); // first2 <--> first1
__first2 = __next;
}
else
__first1 = __first1->_M_next;
}
if (__first2 != __last2) // [first2, last2) <--> this
this->_M_transfer(__first2, __last2);
}
// Splice the node at __i into *this.
void _M_take_one(_List_node_base* __i) // 拿取一个结点
{ this->_M_transfer(__i, __i->_M_next); } // i <--> this
// Splice all nodes from *this after __i.
void _M_put_all(_List_node_base* __i)
{
if (!empty())
__i->_M_transfer(_M_next, this); // [next, last) <--> i
}
};
sort() 使用的归并排序,逐步将 list 拆解成多个有序链表,最后再归并成一个有序 list。
根据上面的算法,list 被拆解成两个有序的链表,最后将多个链表合并成一个有序链表。
下面是 sort() 函数的实现,结合上面两幅图,比较容易看懂原理。
/// list.tcc
template<typename _Tp, typename _Alloc>
void
list<_Tp, _Alloc>::
sort()
{
// Do nothing if the list has length 0 or 1.
if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node
&& this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node)
{
using __detail::_Scratch_list;
// The algorithm used here is largely unchanged from the SGI STL
// and is described in The C++ Standard Template Library by Plauger,
// Stepanov, Lee, Musser.
// Each element of *this is spliced out and merged into one of the
// sorted lists in __tmp, then all the lists in __tmp are merged
// together and then swapped back into *this.
// Because all nodes end up back in *this we do not need to update
// this->size() while nodes are temporarily moved out.
_Scratch_list __carry;
_Scratch_list __tmp[64];
_Scratch_list* __fill = __tmp;
_Scratch_list* __counter;
_Scratch_list::_Ptr_cmp<iterator, void> __ptr_comp;
__try
{
do
{
__carry._M_take_one(begin()._M_node);
for(__counter = __tmp;
__counter != __fill && !__counter->empty();
++__counter)
{
__counter->merge(__carry, __ptr_comp);
__carry.swap(*__counter);
}
__carry.swap(*__counter);
if (__counter == __fill)
++__fill;
}
while ( !empty() );
for (__counter = __tmp + 1; __counter != __fill; ++__counter)
__counter->merge(__counter[-1], __ptr_comp);
__fill[-1].swap(this->_M_impl._M_node);
}
__catch(...)
{
// Move all nodes back into *this.
__carry._M_put_all(end()._M_node);
for (int __i = 0; __i < sizeof(__tmp)/sizeof(__tmp[0]); ++__i)
__tmp[__i]._M_put_all(end()._M_node);
__throw_exception_again;
}
}
}
欢迎关注公众号“源知源为”,阅读更多技术干货
#STL#C/C++ 语言基础
OPPO公司福利 1059人发布
