读了 slist 源码,我主要对整体设计不太懂。在网上看了一些文章,再加上自己的理解,暂时记录下自己的想法。
/* 结构 */
// 自反的含本身指针的结构体
struct _Slist_node_base
{
_Slist_node_base* _M_next;
};
// 继承结构体的模板结构体
template <class _Tp>
struct _Slist_node : public _Slist_node_base
{
_Tp _M_data;
};
/* 全局函数 */
// 将 new_node 链到 prev_node 后面,并返回 new_node
inline _Slist_node_base*
__slist_make_link(_Slist_node_base* __prev_node,
_Slist_node_base* __new_node);
// 从 head 开始遍历,寻找 node 的前驱结点并返回
inline _Slist_node_base*
__slist_previous(_Slist_node_base* __head,
const __Slist_node_base* __node);
// __slist_previous() 的 const 版本,返回一个不可修改的结点
inline const _Slist_node_base*
__slist_previous(const _Slist_node_base* __head,
const _Slist_node_base* __node);
// 将范围[before_first + 1, before_last] 链到 pos 后,
// 相当于将一段链表插入到 pos 后
inline void __slist_splice_after(_Slist_node_base* __pos,
_Slist_node_base* __before_first,
_Slist_node_base* __before_last);
// 相当于 __slist_splice_after(pos, head, __slist_previous(head, 0))
// 即将 head 代表的链插入到 pos 后面
inline void __slist_splice_after(_Slist_node_base* __pos, __Slist_node_base* __head);
// 将范围 [node, __slist_previous(node, 0)] 内的结点反转
inline _Slist_node_base* __slist_reverse(_Slist_node_base* __node);
// 返回范围 [node, __slist_previous(node, 0)] 内的结点数目
inline size_t __slist_size(_Slist_node_base* __node);
这里有一个疑问,为什么要采用继承的方式得到 node 的最终形式呢?
通常的做法是:
template < typename T>
struct Node {
struct Node* next;
T data;
}
Effective C++ 里的 Item 44 说道:
Templates generate multiple classes and multiple functions, so any
template code not dependent on a template parameter causes bloat.
所以这里的设计主要是考虑到目标代码膨胀的问题。按照通常的思路的话,next 指针实际上是与模板参数无关的,而且一些典型的针对链表的操作与结点数据域无关,它们不涉及到数据域的检索和修改等,上面列到的全局函数就是这样的操作。所以把独立于模板参数的成员或者多个实例化体共同的部分抽取出来作为基类,然后继承,可以减少目标代码.
上面列到的全局函数按功能来分,主要是:
删除结点的操作没有加进去,因为删除结点需要释放数据域分配的空间,所以这些全局函数还是比较合理和完备的。这些全局函数很大程度上方便了后面更加复杂函数的构建。如果单纯从适用性上来说,__slist_reverse()
和 __slist_size()
可以用任意合法的结点作为参数,而我之前的思路都是用头结点。
slist 迭代器同样是分层设计,__Slist_iterator
继承自 __Slist_iterator_base
。
struct _Slist_iterator_base
{
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef forward_iterator_tag iterator_category;
_Slist_node_base* _M_node;
_Slist_iterator_base(_Slist_node_base* __x) : _M_node(__x) {}
void _M_incr() { _M_node = _M_node->_M_next; }
bool operator==(const _Slist_iterator_base& __x) const {
return _M_node == __x._M_node;
}
bool operator!=(const _Slist_iterator_base& __x) const {
return _M_node != __x._M_node;
}
};
template <class _Tp, class _Ref, class _Ptr>
struct _Slist_iterator : public _Slist_iterator_base
{
typedef _Slist_iterator<_Tp, _Tp&, _Tp*> iterator;
typedef _Slist_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
typedef _Slist_iterator<_Tp, _Ref, _Ptr> _Self;
typedef _Tp value_type;
typedef _Ptr pointer;
typedef _Ref reference;
typedef _Slist_node<_Tp> _Node;
_Slist_iterator(_Node* __x) : _Slist_iterator_base(__x) {}
_Slist_iterator() : _Slist_iterator_base(0) {}
_Slist_iterator(const iterator& __x) : _Slist_iterator_base(__x._M_node) {}
reference operator*() const { return ((_Node*) _M_node)->_M_data; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
_Self& operator++()
{
_M_incr();
return *this;
}
_Self operator++(int)
{
_Self __tmp = *this;
_M_incr();
return __tmp;
}
};
可以看到成员_M_node
, operator==
和 operator!=
是不依赖与模板参数的,而 operator*
和 operator->
实际上都和模板参数有关,具体来说就是和数据域有关。而 _M_incr()
作为基类的成员,因为它不仅与模板参数无关,而且它是Self& operator++()
和 Self operator++(int)
的共同部分,抽取出来作为基类的成员是很自然的。
至于Self& operator++()
和 Self operator++(int)
到底有啥区别? Self& operator++()
是前置递增运算符重载;Self operator++(int)
是后置递增运算符重载。C++ Primer中关于递增和递减运算符重载的 Best Pratices:
定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符通常应该被定义成类的成员。
slist 的迭代器是一个 forward iterator (Refinement of Input Iterator, Output Iterator),Refinement 在 stl 中表示 Concepts 之间的一种类似C++类的“继承关系”(A concept: a set of type requirements)。
Input Iterator 必须支持:
Output Iterator 必须支持:
forward iterator 同时具有Input Iterator 和 Output Iterator 的特性,那么实际上它有六种 requirements. 解引用既可以出现在赋值运算符的右侧,也可以出现在左侧。而且 forward iterator 可以多遍扫描。
用 list
验证一下:
int arr[] = {1, 2, 3, 4, 5, 6, 7};
size_t size = sizeof(arr) / sizeof(int);
list<int> mylist(arr, arr + size);
list<int>::iterator it = mylist.begin();
for (; it != mylist.end(); ++it)
if (0 == *it % 2)
*it = 0;
for (it = mylist.begin(); it != mylist.end(); ++it)
cout << *it << ' ';
cout << endl;
运行结果:1 0 3 0 5 0 7
An slist is a singly linked list: a list where each element is linked to the next element, but not to the previous element. That is, it is a Sequence that supports forward but not backward traversal, and (amortized) constant time insertion and removal of elements.
上面是 SGI_STL 文档中关于 slist 的定义。
slist is Model of Front Insertion Sequence .
Front Insertion Sequence is Refinement of Sequence.
Sequence is Refinement of Forward Container .
Forward Container is Refinement of Container.
下面从 Container 谈起,一步步过渡到 slist。
Container 是存放其他对象的对象,并且有方法能够访问它的元素。每种类型都是一个 Container 的模型,并且有一个与之想联系的迭代器类型,用来迭代访问容器中的元素。
Container 分为三种:
(1)sequence containers
序列容器中元素的顺序是由用户指定的。
(2)associative containers
关联容器中,元素以一定的顺序插入,例如,升序。关联容器主要分为两种:maps 和 sets。
(3)container adapters
容器适配器是序列容器或是关联容器的变种,对接口做了限制,不支持迭代器。
Container 的相关表达式语法:
Name | Expression | Precondition | Semantics | Postcondition |
---|---|---|---|---|
Copy constructor | X(a) | X().size() == a.size(). X() 包含了a 中元素的拷贝 | ||
Copy constructor | X b(a) | b.size() == a.size(). b 包含了a 中所有元素的拷贝 | ||
Assignment operator | b = a | b.size() == a.size(). b 包含了a 中所有元素的拷贝 | ||
Destructor | a.~X() | a 中每个元素都被destroyed,并且分配的内存被回收 | ||
range 的开始 | a.begin() | 返回一个迭代器,指向容器中第一个元素 | 当 a.size() == 0 时,a.begin() 指向空,此时不可解引用,其他情况都可以。 | |
range 的结束 | a.end() | 返回一个指向越过最后元素一个单位的位置的迭代器 | a.end() 是越界的,通常是空。 | |
Size | a.size() | 返回容器中元素个数的多少,这也称为容器的大小 | a.size() >= 0 && a.size() <= a.max_size() | |
Maximum size | a.max_size() | 返回一个容器可以有的最大容纳元素的多少 | a.max_size() >= 0 && a.max_size() >= a.size() | |
指示容器是否为空 | a.empty() | 与 a.size() == 0相等,但是可能更快(大概是因为没有封装a.size() == 0) | ||
Swap | a.swap(b) | 与swap(a, b) 相等 |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
来看看 slist 中 满足 Container 表达式的部分:
Container中表达式 | slist 中对应部分 |
---|---|
Copy Constructor | slist(const slist& __x) |
Assignment operator | slist& operator= (const slist& __x) |
Destructor | ~slist() |
Beginning of range | iterator begin() |
End of range | iterator end() |
Size | size_type size() |
Maximum size | size_type max_size() |
Emtpy | bool empty() |
Swap | void swap(slist& __x) |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
关于 Container 还有比较重要的部分。
借助于迭代器,STL 的算法可以与具体的类型分隔开,而这些算法通常作用于一个迭代器的左闭右开的区间,叫做range。对于容器来说,合法的 range 是[begin(), end())
。
Forward Container 的元素按照一定顺序排列。这个给定的顺序可以让元素按照相等(如果容器的元素是 Equality Comparable)和字典序(如果容器的元素是 LessThan Comparable )进行排列。
实际上,Forward Container 是 Refinement of Container, EqualityComparable 和 LessThanComparable.
一个类型如果是 EqualityComparable 的话,那么这种类型的对象可以用运算符“==”比较是否相等。
一个类型如果是 LessThanComparable 的话,它必须能够用运算符“<”比较两个对象,并且运算符“<”是偏序关系。
Forward Container 的表达式语义
Name | Expression | Precondition | Semantics | Postcondition |
---|---|---|---|---|
Equality | a == b | 当a.size() == b.size(),并且a中每个元素与b中相对应元素相等,则返回 true,否则返回false | ||
Less | a < b | 与 lexicographical_compare(a, b)相等 |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
slist中对应的部分
Forward Container 中表达式 | 运算符 | slist 中对应部分 |
---|---|---|
Equality | == | bool operator==(const slist& _SL1, const slist& _SL2) |
Inequality | != | bool operator!=(const slist& _SL1, const slist& _SL2) |
Less | < | bool operator<(const slist& _SL1, const slist& _SL2) |
Greater | > | bool operator>(const slist& _SL1, const slist& _SL2) |
Less or equal | <= | bool operator<=(const slist& _SL1, const slist& _SL2) |
Greater or equal | >= | bool operator>=(const slist& _SL1, const slist& _SL2) |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
在这6个运算符中,只有“==”和 “<” 是根本的,其他都是调用这两个运算符的函数。
// operator ==
template <class _Tp, class _Alloc>
inline bool operator==(const slist<_Tp,_Alloc>& _SL1,
const slist<_Tp,_Alloc>& _SL2) {
typedef typename slist<_Tp,_Alloc>::const_iterator const_iterator;
const_iterator __end1 = _SL1.end();
const_iterator __end2 = _SL2.end();
const_iterator __i1 = _SL1.begin();
const_iterator __i2 = _SL2.begin();
while (__i1 != __end1 && __i2 != __end2 && *__i1 == *__i2) {
++__i1;
++__i2;
}
return __i1 == __end1 && __i2 == __end2;
}
// operator <
template <class _Tp, class _Alloc>
inline bool operator<(const slist<_Tp,_Alloc>& _SL1,
const slist<_Tp,_Alloc>& _SL2) {
return lexicographical_compare(_SL1.begin(), _SL1.end(),
_SL2.begin(), _SL2.end());
}
“!=” 定义的逻辑很简单,就是 “==”求反即可。
template <class _Tp, class _Alloc>
inline bool
operator!=(const slist<_Tp,_Alloc>& _SL1, const slist<_Tp,_Alloc>& _SL2) {
return !(_SL1 == _SL2);
}
而“>”的定义是因为“<”具有对称性,如果 a < b 那么必然 b > a。所以如果要判断a > b 否,判断 b < a 即可。
template <class _Tp, class _Alloc>
inline bool
operator>(const slist<_Tp,_Alloc>& _SL1, const slist<_Tp,_Alloc>& _SL2) {
return _SL2 < _SL1;
}
至于“<=”是个什么逻辑呢?实际上也很简单,“<=” 就是 !“>”,小于等于就是不大于,对“>”求反即可,当然这里是直接调用 “<”的。如果要判断 a <= b,判断 !(b < a)即可。
template <class _Tp, class _Alloc>
inline bool
operator<=(const slist<_Tp,_Alloc>& _SL1, const slist<_Tp,_Alloc>& _SL2) {
return !(_SL2 < _SL1);
}
类似的如果要判断 a >= b,判断 !(a < b) 即可。
template <class _Tp, class _Alloc>
inline bool
operator>=(const slist<_Tp,_Alloc>& _SL1, const slist<_Tp,_Alloc>& _SL2) {
return !(_SL1 < _SL2);
}
简单总结一下这些关系的推导:
a!=b⟺!(a==b)
a>b⟺b<a
a<=b⟺!(b<a)
a>=b⟺!(a<b)
Sequence 是一个可变长的容器,它的元素按照严格的线性顺序排列,支持元素的插入和删除。
Sequence is Refinement of Forward Container and Default Constructible.
Forward Container 前面谈过了,来看看 Default Constructible。
如果一个类型是 Default Constructible 的话,它必然有一个默认的构造函数,即,可以构造一个对象但不用任何值来初始化它。通常的语法:X() 或者 X x。
Sequence 表达式语义:
Name | Expression | Precondition | Semantics | Postcondition |
---|---|---|---|---|
Fill constructor | X(n, t) | n >=0 | 创建一个sequence,它的元素是 t 的 n 份拷贝 | size() ==n. 每个元素都是 t 的一份拷贝 |
Fill constructor | X a(n, t) | n >=0 | 创建一个sequence,它的元素是 t 的 n 份拷贝 | a.size() ==n. 每个元素都是 t 的一份拷贝 |
Default fill constructor | X(n) | n >= 0 | 创建一个含有 n 个元素的 sequence,每个元素被初始化为默认值 | size() == n,每个元素都是 T() 的一份拷贝 |
Default fill constructor | X a(n) | n >= 0 | 创建一个含有 n 个元素的 sequence,每个元素被初始化为默认值 | a.size() == n,每个元素都是 T() 的一份拷贝 |
Default constructor | X a; or X() | 与X(0)相等 | size() == 0 | |
Range constructor | X(i, j) | [i, j) 是合法的range | 创建一个range[i, j)的拷贝的sequence | size() 与 i 到 j 的距离相等。每个元素与都是范围内对应的元素的拷贝 |
Range constructor | X a(i, j) | [i, j) 是合法的range | 创建一个range[i, j)的拷贝的sequence | a.size() 与 i 到 j 的距离相等。每个元素与都是范围内对应的元素的拷贝 |
Front | a.front() | !a.emtpy() | 与*(a.first())相等 | |
Insert | a.insert(p, t) | p 是 a 中一个合法的迭代器;a.size() < a.max_size() | 一个 t 的拷贝被插入到 p 所指的位置前 | a.size() 增加了1。*(a.insert(p,t))是一份 t 的拷贝 |
Fill insert | a.insert(p, n, t) | p 是一个合法的迭代器,n >= 0 && a.size() + n <= a.max_size() | n 份 t 的拷贝被插入到 p 之前 | a.size() 增加了 n |
Range insert | a.insert(p, i, j) | [i, j) 是合法的 range;a.size() 加上从 i 到 j 的距离没有超过a.max_size() | 在 p 之前插入 range[i, j) 的拷贝 | a.size() 增加了从 i 到 j 的距离 |
Erase | a.erase(p) | p 是一个可以解引用的迭代器 | 移除 p 所指的元素并将其销毁 | a.size() 减少了1 |
Range erase | a.erase(p, q) | [p, q) 是一个合法的范围 | 销毁[p, q)范围内的元素,并将其移除 | a.size() 减少了从 p 到 q 的距离 |
Clear | a.clear() | 与a.erase(a.begin(), a.end())相等 | ||
Resize | a.resize(n, t) | n <= a.max_size() | 修改容器使得它正好有 n 个元素。如果有任何元素要插入的话,它必然是 t 的拷贝。如果 n > a.size(),执行a.insert(a.end(), n - size(), t). 如果 n < a.size(), 执行a.erase(a.begin() + n, a.end()) | a.size() == n |
Resize | a.resize(n) | n <= a.max_size() | 与 a.resize(n, T())相等 | a.size() == n |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
下面是 Sequence 中规定的语义与 slist 中函数的对应:
Sequence 规定的表达式 | slist 中对应的函数 |
---|---|
Fill contructor | slist(size_type __n, const value_type& __x) |
Default fill constructor | explicit slist(size_type __n) |
Default constructor | explicit slist(const allocator_type __a) |
Range constructor | 1、slist(_InputIterator __first, _InputIterator __last); 2、slist(const_iterator __first, const_iterator __last); 、 slist(const value_type __first, const value_type* __last); |
Front | reference front() |
Insert | iterator insert(iterator __pos, const value_type& __x) |
Fill insert | iterator insert(iterator __pos, size_type __n, const value_type& __x) |
Range insert | 1、void insert(iterator __pos, _InIter __first, _InIter __last); 2、void insert(iterator __pos, const_iterator __first, const_iterator __last); 、void insert(iterator __pos, const value_type __first, const value_type* __last); |
Erase | iterator erase(iterator __pos) |
Range erase | iterator erase(iterator __first, iterator __last) |
Clear | void clear() |
Resize | void resize(size_type new_size, const _Tp& __x); void resize(size_type new_size); |
Front Insertion Sequence 是可以在 sequence 的开头插入元素并访问的 sequence。
Front Insertion Sequence 定义的表达式比较简单:
Name | Expression | Precondition | Semantics | Postcondition |
---|---|---|---|---|
Front | a.front() | !a.empty() | 与 *(a.begin())相等 | |
Push front | a.push_front(t) | 与a.insert(a.begin(), t) | a.size() 增加了1,a.front() 是 t 的一份拷贝 | |
Pop front | a.pop_front() | !a.empty() | 与a.erase(a.begin()) | a.size() 增加了1 |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
slist中对应部分:
Front Insertion Sequence 定义的表达式 | slist 对应的函数 |
---|---|
Push front | void push_front(const value_type& __x); void push_front(); |
Pop front | void pop_front() |
-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·
slist 的层次是比较复杂的。类 slist 是对 类 _Slist_base 的私有继承,而 _Slist_base 的结构有两种形式:
template
struct _Slist_base: public _Slist_alloc_base<_Tp, _Alloc,
_Alloc_traits<_Tp, _Alloc>::_S_instanceless> {
...
}
// _Slist_alloc_base
// Base for general standard-conforming allocators.
template
class _Slist_alloc_base {
...
}
// Specialization for instanceless allocators.
template
class _Slist_alloc_base<_Tp,_Allocator, true> {
...
}
template
struct _Slist_base {
...
}
对 _Slist_alloc_base 进行偏特化处理后,节省了一个不必要对象的空间,省去了实例化对象的麻烦,具体体现就是省去了 _M_node_allocator
这个变量成员,在不继承的 _Slist_base 中也没有该变量成员。
同时因为 _S_instanceless == true
,所以 _Alloc_traints 增加一个成员_Alloc
,这个成员有两个静态方法,可以直接调用,替代了 _M_node_allocator
的功能。
static _Tp* allocate(size_t)
static void deallocate(_Tp*, size_t)
如果从继承 _Slist_alloc_base 的 _Slist_base 来看,它实际上只做了一件事,叫做 _M_erase_after
,这实际上是两个方法重载,分别是:
// 删除 '__pos' 的下一个结点
_Slist_node_base* _M_erase_after(_Slist_node_base* __pos) {
...
destroy(&__next->_M_data);
_M_put_node(__next);
...
}
// 删除 ['__before_first' + 1, '__last_node')
_Slist_node_base* _M_erase_after(_Slist_node_base* __before_first, _Slist_node_base* __last_node) {
...
destroy(&__tmp->_M_data);
_M_put_node(__tmp);
...
}
删除操作就算对象是指针,也要涉及到数据域的内存回收。
Allocator做的事内存的分配和回收。
_Slist_node<_Tp>* _M_get_node() {
return _Alloc_type::allocate(1);
}
void _M_put_node(_Slist_node<_Tp>* __p) {
_Alloc_type::deallocate(__p, 1);
}
那么 _Slist_base 的主要功能就是初始化链表头,进行删除操作?
_Slist_base 存在的意义到底是什么?
首先 _Slist_alloc_base 担当了 Allocator 的作用,它的意义毋庸置疑。
可以看到 slist 中没有自己的变量成员,变量成员全部从继承得来,也许 _Slist_base 起到盛放底层数据结构和操作的容器作用,是 Allocator 和 slist 之间的一个缓冲,也许又有编译方面的考虑吧,不得而知。
slist 的很多成员函数或者与之相关的全局函数都是从 Container 到 Front Insertion Sequence 中“继承”而来。这节主要列举 slist 私有的成员函数以及一些独有的公有成员函数。
_Node* _M_create_node(const value_type& __x);
_Node* _M_create_node();
_Node* _M_insert_after(_Node_base* __pos, const value_type& __x);
_Node* _M_insert_after(_Node_base* __pos);
void _M_insert_after_fill(_Node_base* __pos,
size_type __n,
const value_type& __x);
template <class _InIter>
void _M_insert_after_range(_Node_base* __pos,
_InIter __first,
_InIter __last);
template <class _Integer>
void _M_insert_after_range(_Node_base* __pos,
_Integer __n,
_Integer __x,
__true_type);
template <class _Integer>
template <class _InIter>
void _M_insert_after_range(_Node_base* __pos,
_InIter __first,
_InIter __last,
__false_type);
void _M_insert_after_range(_Node_base* __pos,
const_iterator __first,
const_iterator __last);
void _M_insert_after_range(_Node_base* __pos,
const value_type* __first,
const value_type* __last);
void reverse();
void remove(const _Tp& __val);
void unique();
void merge(slist& __x);
void sort();
template <class _Predicate>
void remove_if(_Predicate __pred);
template <class _BinaryPredicate>
void unique(_BinaryPredicate __pred);
template <class _StrictWeakOrdering>
void merge(slist&, _StrictWeakOrdering);
template <class _StrictWeakOrdering>
void sort(_StrictWeakOrdering __comp);
参考资料:
[1] SGI Standard Template Library Programmer’s Guide
[2] 《STL源码剖析》之序列式容器(2) slist
[3] 《C++ Primer(第五版)》