STL 中 list 实现的一些问题
我的小型STL库:https://github.com/Daghlny/uSTL
list 的底层实现是一个 双向循环链表。会有一个 __list_node<T>
节点作为标志,这个节点的 next 指向的是 begin() 返回的节点,这个节点本身是 end() 的返回。
- const_iterator 最好实现成一个独特的类,因为
iterator< const T>
根本不能实现,而且模板的偏特化是不能针对 const 类型做的。 - list 内部最好不要直接存储链表头和链表尾的 iterator,因为有一些函数可以直接通过 iterator 修改 list 内部的结构(比如其他 list 调用 splice,将本 list 置空),此时就无法捕获到 list 内部的变化,因此需要每次调用的时候重新生成一个 head。此时就要用到前面提到的标志节点。
- 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