Última actualización:2026-10-12
STL set y multiset¶
set/multiset usa rb_tree como estructura subyacente, por lo que sus elementos se ordenan automáticamente. El criterio de ordenación es la key, y en los elementos de set/multiset value y key se funden en uno solo: el value es la key.
No podemos usar los iterators de set/multiset para cambiar el valor de los elementos (porque la key tiene sus estrictas reglas de ordenación). El iterator de set/multiset es el const-iterator del RB tree subyacente, precisamente para prohibir que el usuario asigne a los elementos.
La key de un elemento de set debe ser única, por lo que su insert usa _M_insert_unique() de rb_tree, mientras que la key de un elemento de multiset puede repetirse, por lo que su insert usa _M_insert_equal() de rb_tree.
1.set¶
Como el código fuente de set es bastante sencillo, partimos de las preguntas siguientes.
Primera pregunta: la key es el value, y el value también es la key.
El código concreto aparecerá en la segunda pregunta; aquí damos la lógica interna del código que solemos escribir: vemos que dentro hay un árbol rojo-negro, y en la definición del árbol rojo-negro la key y el value son lo mismo, así que con eso queda respondida la pregunta. (En el código fuente los typedef provienen todos de 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.
};

Segunda pregunta: no se puede usar el iterador para cambiar el valor de los elementos.
Lo de no poder usar el iterador para cambiar el valor de los elementos lo comprobamos en el iterador que viene a continuación: descubrimos que todo usa const_iterator, con lo que también queda respondida la segunda pregunta.
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;
};
Tras el análisis anterior nos vienen a la memoria queue, priority_queue y stack: todos ellos usan un contenedor subyacente, por eso se llaman adaptadores de contenedor; y set también usa un contenedor subyacente, así que igualmente puede llamársele container adapter, es decir, adaptador de contenedor.
Tercera pregunta: la inserción usa una key única.
En la capa inferior se llama a _M_insert_unique.
template<typename _InputIterator>
set(_InputIterator __first, _InputIterator __last)
: _M_t()
{ _M_t._M_insert_unique(__first, __last); }
Echemos un vistazo sencillo a la implementación de esta función:
El _M_get_insert_unique_pos de abajo devuelve un pair; si la key insertada coincide con otra, el second del pair es 0; según sea o no 0 se puede saber si la key está duplicada. Al comprobarlo en el código de abajo, cuando second no es 0, es decir, cuando no está duplicada, se puede insertar directamente: en ese caso se construye directamente un pair cuyo second es true; en caso contrario se construye un pair cuyo second es 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 ) );
}
Miremos también la función mencionada antes:
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);
}
El uso del pair anterior me ha dado una idea: si se puede usar así, aprendámoslo:
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¶
Análogamente, multiset es básicamente igual que set en su definición; la diferencia está en que la inserción usa otra función, ¡y eso es justo lo que le permite completar la inserción de keys repetidas!
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));
}
Seguimos rastreando la función _M_get_insert_equal_pos anterior:
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);
}
Temas relacionados¶
- Análisis del código fuente de STL array | C++那些事
- Análisis profundo de STL string | C++那些事
- STL string preguntas de entrevista explicadas | C++那些事
- Análisis del código fuente de STL deque | C++那些事
- STL optimización de clase base vacía EBO | C++那些事
- STL implementación de una tabla hash escrita a mano | C++那些事
- STL hashtable tabla hash | C++那些事
- Análisis del código fuente de los iteradores STL | C++那些事
- Análisis del código fuente de STL list | C++那些事
- STL map y multimap | C++那些事