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++基础 文章被收录于专栏

C/C++ 语言基础

全部评论

相关推荐

字节 飞书绩效团队 (n+2) * 15 + 1k * 12 + 1w
点赞 评论 收藏
分享
10-05 11:11
海南大学 Java
投票
理想江南137:感觉挺真诚的 感觉可以试一试
点赞 评论 收藏
分享
2 2 评论
分享
牛客网
牛客企业服务