Последнее обновление:2026-10-12
STL set и multiset¶
set/multiset построены на базе rb_tree, поэтому обладают свойством автоматической сортировки элементов. Основой сортировки служит key, а у элементов set/multiset value и key объединены воедино: value и есть key.
Мы не можем изменять значения элементов через iterators контейнеров set/multiset (поскольку key подчиняется своим строгим правилам упорядочивания). Итератор set/multiset — это const-iterator нижележащего RB tree, именно чтобы запретить пользователю присваивать значения элементам.
key элемента set должен быть уникальным, поэтому его insert использует _M_insert_unique() из rb_tree, тогда как key элемента multiset может повторяться, и его insert использует _M_insert_equal() из rb_tree.
1. set¶
Исходники set довольно просты, поэтому пойдём от следующих вопросов.
Первый вопрос: key — это value, и value — это key.
Конкретный код будет во втором вопросе; здесь покажем внутреннюю логику привычного нам кода: мы видим, что внутри есть красно-чёрное дерево, а в его определении key и value совпадают — это и есть ответ на вопрос. (typedef в исходниках все берутся от key).
template<typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set
{
// concept requirements
typedef typename _Alloc::value_type _Alloc_value_type;
public:
// typedefs:
//@{
/// Public typedefs.
typedef _Key key_type;
typedef _Key value_type; // value也是key
typedef _Compare key_compare;
typedef _Compare value_compare;
typedef _Alloc allocator_type;
//@}
private:
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.
};

Второй вопрос: невозможно изменить значение элемента с помощью итератора.
То, что значение элемента нельзя изменить через итератор, мы увидим, взглянув на дальнейшие итераторы: везде используется const_iterator, — так что и на второй вопрос получен ответ.
template<typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set
{
private:
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.
public:
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
};
После проведённого анализа вспоминаются queue, priority_queue, stack: все они используют нижележащие контейнеры и потому называются адаптерами контейнеров. set тоже использует нижележащий контейнер, поэтому его тоже можно назвать container adapter, то есть адаптером контейнера.
Третий вопрос: вставляемый key уникален.
Внизу вызывается _M_insert_unique.
template<typename _InputIterator>
set(_InputIterator __first, _InputIterator __last)
: _M_t()
{ _M_t._M_insert_unique(__first, __last); }
Взглянем кратко на реализацию этой функции:
Ниже _M_get_insert_unique_pos возвращает pair; если вставляемый key совпадает с существующим, second пары равен 0, и по тому, равен ли он нулю, можно судить о повторе key. В коде ниже при проверке, когда second не равен 0, то есть повтора нет, можно вставлять напрямую: тут же конструируется pair с second, равным true, иначе конструируется pair с second, равным false.
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
template<typename _Arg>
#endif
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::iterator, bool>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_insert_unique( _Arg && __v )
{
typedef pair<iterator, bool> _Res;
pair<_Base_ptr, _Base_ptr> __res
= _M_get_insert_unique_pos( _KeyOfValue() ( __v ) );
if ( __res.second )
return(_Res( _M_insert_( __res.first, __res.second,
_GLIBCXX_FORWARD( _Arg, __v ) ),
true ) );
return(_Res( iterator( static_cast<_Link_type>(__res.first) ), false ) );
}
Посмотрим и на упомянутую выше функцию:
template<typename _Key, typename _Val, typename _KeyOfValue,typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr,typename _Rb_tree<_Key, _Val, _KeyOfValue,Compare, _Alloc>::_Base_ptr>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_unique_pos(const key_type& __k)
{
// typedef pair
typedef pair<_Base_ptr, _Base_ptr> _Res;
// _x表示当前节点,_y表示_x的父节点
_Link_type __x = _M_begin();
_Link_type __y = _M_end();
bool __comp = true;
// 寻找插入点
while (__x != 0)
{
__y = __x;
// __k<__x是否为true
__comp = _M_impl._M_key_compare(__k, _S_key(__x));
// __k<__x就往左孩子查找,否则右孩子查找
__x = __comp ? _S_left(__x) : _S_right(__x);
}
iterator __j = iterator(__y);
// __k<__y,往__y的左孩子插入节点即可,不是做插入,是找到位置即可。
if (__comp)
{
// 特殊位置
if (__j == begin())
return _Res(__x, __y);
else
--__j; // 左孩子 这里调用了--操作符
}
// __j<__k,返回当前节(__x=0)点与父节点
if (_M_impl._M_key_compare(_S_key(__j._M_node), __k))
return _Res(__x, __y);
// _j>=__k,插入失败
return _Res(__j._M_node, 0);
}
Использование pair выше натолкнуло меня на мысль — оказывается, так можно; давайте изучим:
cout<<"flag: "<<itree._M_insert_unique(5).second<<endl; // 学习返回值
typedef pair<int ,bool> _Res; // 也来用一下typedef后的pair
cout<<_Res(1,true).first<<endl; // 直接包裹
_Res r=make_pair(2,false); // 定义新对象
cout<<r.first<<endl; // 输出结果
2. multiset¶
Аналогично, определение multiset в целом похоже на set; отличие в том, что для вставки используется другая функция — именно она и позволяет ему выполнять вставку повторяющихся key!
template<typename _InputIterator>
multiset(_InputIterator __first, _InputIterator __last)
: _M_t()
{ _M_t._M_insert_equal(__first, __last); }
_M_insert_equal:
typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_insert_equal(_Arg&& __v)
{
pair<_Base_ptr, _Base_ptr> __res = _M_get_insert_equal_pos(_KeyOfValue()(__v));
return _M_insert_(__res.first, __res.second, _GLIBCXX_FORWARD(_Arg, __v));
}
Продолжим отслеживать упомянутую выше функцию _M_get_insert_equal_pos:
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr,
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_equal_pos(const key_type& __k)
{
typedef pair<_Base_ptr, _Base_ptr> _Res;
_Link_type __x = _M_begin();
_Link_type __y = _M_end();
while (__x != 0)
{
__y = __x;
__x = _M_impl._M_key_compare(__k, _S_key(__x)) ?
_S_left(__x) : _S_right(__x);
}
return _Res(__x, __y);
}
Похожие темы¶
- Анализ исходников STL array | C++那些事
- Глубокий анализ STL string | C++那些事
- Разбор вопросов собеседования про STL string | C++那些事
- Анализ исходников STL deque | C++那些事
- Оптимизация пустого базового класса EBO в STL | C++那些事
- Реализация хеш-таблицы своими руками | C++那些事
- Хеш-таблица STL hashtable | C++那些事
- Анализ исходников STL итераторов | C++那些事
- Анализ исходников STL list | C++那些事
- STL map и multimap | C++那些事