本文涉及到 SGI STL 源码的文件主要有 stl_set.hstl_multiset.hset.hmultiset.hset 等文件。

1. set 简介

set 即集合,相比于其他容器有些特别。首先是它的每个元素是唯一的,即不允许有相同的值出现。其次,作为一种关联容器,set 的元素不像 map 那样可以同时拥有实值(value)和键值(key),set 元素的键值就是实值,实值就是键值。
由于 set 的实质和键值相同,共用同一个内存空间,而 set 的底层容器为红黑树(中序遍历有序),因此不能对其键值进行修改,否则会破坏其有序特性。为避免非法修改操作,在SGI STL的实现中,set<T>::iterator 被定义为 RB-tree 底层的 const_iterator,_杜绝写入操作。set 与 list 有一个相似的地方是,元素插入、删除后,之前的迭代器依然有效(被删除的那个元素的迭代器除外)。
我们知道集合有一些特殊的操作,诸如并、交、差等,在STL的 set 中,默认也提供了这些操作,如交集 set_intersection 、联集 set_union 、差集 set_difference 和对称差集 set_symmetric_difference 等。与之前那些线性容器不同的是,这些 set 的操作并不是在 set 内部实现的,而是放在了算法模块(algorithm)中,其具体实现在后面的算法章节中会具体介绍。

2. set 的实现

前面多次提到 set 的底层采用 RB-tree 容器,这是因为 RB-tree 是一种比较高效的平衡二叉搜索树,能够很好的满足元素值唯一的条件,而且查找效率高。由于 RB-tree 已实现了很多操作,因此 set 基本上只是对 RB-tree 进行了一层简单的封装。下面是其实现的主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template <class _Key, class _Compare, class _Alloc>
class set {
public:
  typedef _Key     key_type;
  typedef _Key     value_type; // 实值与键值同类型
private:
  typedef _Rb_tree<key_type, value_type, _Identity<value_type>, key_compare, _Alloc> _Rep_type;
  _Rep_type _M_t;  // 底层使用红黑树作为容器
  set() : _M_t(_Compare(), allocator_type()) {} // 默认构造函数
  set(const set<_Key,_Compare,_Alloc>& __x) : _M_t(__x._M_t) {}  // 拷贝构造函数
  pair<iterator,bool> insert(const value_type& __x) { // 插入操作
    pair<typename _Rep_type::iterator, bool> __p = _M_t.insert_unique(__x);
    return pair<iterator, bool>(__p.first, __p.second);
  }
  void erase(iterator __position) { // 删除操作
    typedef typename _Rep_type::iterator _Rep_iterator;
    _M_t.erase((_Rep_iterator&)__position);
  }
  void clear() { _M_t.clear(); } // 清空操作
  iterator find(const key_type& __x) const { return _M_t.find(__x); } // 查找
  size_type count(const key_type& __x) const { // 计数
    return _M_t.find(__x) == _M_t.end() ? 0 : 1;
  }
};
template <class _Key, class _Compare, class _Alloc>
inline bool operator==(const set<_Key,_Compare,_Alloc>& __x,
                       const set<_Key,_Compare,_Alloc>& __y) { // 比较相等操作符
  return __x._M_t == __y._M_t;
}
template <class _Key, class _Compare, class _Alloc>
inline bool operator<(const set<_Key,_Compare,_Alloc>& __x,
                      const set<_Key,_Compare,_Alloc>& __y) { // 比较大小操作符
  return __x._M_t < __y._M_t;
}

可以看到基本都是调用 _M_t 的方法来实现的,而这里的 _M_t 是一个红黑树对象。

3. multiset

multiset 的特性和用法与 set 基本相同,唯一差别在于它允许有重复的键值,因此它的插入操作使用的底层机制是 RB-tree 的 insert_equal() 而不是 insert_unique() ,下面是 multiset 的主要代码,主要列出了与 set 不同的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <class _Key, class _Compare, class _Alloc>
class multiset {
public:
  multiset(const value_type* __first, const value_type* __last)
    : _M_t(_Compare(), allocator_type())
    { _M_t.insert_equal(__first, __last); } // 构造函数
  iterator insert(const value_type& __x) { // 插入操作
    return _M_t.insert_equal(__x);
  }
  iterator insert(iterator __position, const value_type& __x) {
    typedef typename _Rep_type::iterator _Rep_iterator;
    return _M_t.insert_equal((_Rep_iterator&)__position, __x);
  }
  void insert(const value_type* __first, const value_type* __last) {
    _M_t.insert_equal(__first, __last);
  }
  void insert(const_iterator __first, const_iterator __last) {
    _M_t.insert_equal(__first, __last);
  }
};

其他部分基本与 set 一样。

Original Link: http://ibillxia.github.io/blog/2014/08/17/insight-into-stl-4-associative-containers-2-set-and-multiset/
Attribution - NON-Commercial - ShareAlike - Copyright © Bill Xia