STL 中 list 实现的一些问题

我的小型STL库:https://github.com/Daghlny/uSTL

list 的底层实现是一个 双向循环链表。会有一个 __list_node<T> 节点作为标志,这个节点的 next 指向的是 begin() 返回的节点,这个节点本身是 end() 的返回。

  1. const_iterator 最好实现成一个独特的类,因为 iterator< const T> 根本不能实现,而且模板的偏特化是不能针对 const 类型做的。
  2. list 内部最好不要直接存储链表头和链表尾的 iterator,因为有一些函数可以直接通过 iterator 修改 list 内部的结构(比如其他 list 调用 splice,将本 list 置空),此时就无法捕获到 list 内部的变化,因此需要每次调用的时候重新生成一个 head。此时就要用到前面提到的标志节点。
  3. list 的 size() 方法为了迁就 splice 系列方法,大多数都实现成了O(n)复杂度。我看 cppreference 上写 since C++11 之后就是 O(1) 复杂度,但是又看了一下 g++ 版本的 list 实现,发现代码是:
template<typename _Tp>
struct _List_iterator {
    typedef _List_iterator<_Tp>              _Self;
    /*其他的一些定义*/
    typedef std::bidirectional_iterator_tag   iterator_category;
    /*其他的成员*/
};

iterator
begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_node._M_next); }

size_type
size() const _GLIBCXX_NOEXCEPT
{ return std::distance(begin(), end()); }

首先 size() 调用 distance 来算距离,begin() 返回的是 iterator_category 为 双向迭代器 的,因此 distance 方法肯定是一步一步往前走的,复杂度还是 O(n)。
当然,size() 也没有针对 C++11 进行优化,g++版本里,对 C++11 进行优化的都会用宏来标注出来,如:

#if __cplusplus >= 201103L

18-04-23 更新

今天看另一台服务器上的实现,发现具体的代码变了,如图所示:

size_type
size() const _GLIBCXX_NOEXCEPT
{ return this->_M_node_count(); }

#if _GLIBCXX_USE_CXX11_ABI
size_t _M_node_count() const {return _M_impl._M_node._M_data; }
#else
size_t _M_node_count() const {
    return _S_distance(_M_impl._M_node._M_next, std::__addressof(_M_impl._M_node));
}

并且,在每次调用 merge 或者 splice 时,是会改变 _M_data 的值的,例如 merge 的实现:

void merge(list& _x) {
    /* 其他的工作 */
    this->_M_inc_size(_x._M_get_size());
    _x._M_set_size(0);
}

这样果然就可以保证 O(1) 时间的 size()
看了一下,第一次的服务器(阿里云)gcc 的版本是 4.8.5,第二次的服务器(实验室的)版本是 5.4.0

posted on 2018-04-17 19:31  daghlny  阅读(502)  评论(0编辑  收藏  举报

导航