Hay una imagen bien conocida (hoja de trucos) llamada "C ++ Container choice". Es un diagrama de flujo para elegir el mejor contenedor para el uso deseado.
¿Alguien sabe si ya hay una versión de C ++ 11?
Este es el anterior:
Hay una imagen bien conocida (hoja de trucos) llamada "C ++ Container choice". Es un diagrama de flujo para elegir el mejor contenedor para el uso deseado.
¿Alguien sabe si ya hay una versión de C ++ 11?
Este es el anterior:
Respuestas:
No que yo sepa, sin embargo, se puede hacer textualmente, supongo. Además, el gráfico está ligeramente apagado, porque list
no es un contenedor tan bueno en general, y tampoco lo es forward_list
. Ambas listas son contenedores muy especializados para aplicaciones de nicho.
Para construir dicho gráfico, solo necesita dos pautas simples:
Preocuparse por el rendimiento suele ser inútil al principio. Las grandes consideraciones de O realmente solo entran en vigencia cuando comienzas a manejar unos pocos miles (o más) de artículos.
Hay dos grandes categorías de contenedores:
find
operacióny entonces usted puede construir varios adaptadores en la parte superior de ellos: stack
, queue
, priority_queue
. Dejaré los adaptadores aquí, están lo suficientemente especializados como para ser reconocibles.
Pregunta 1: ¿ asociativa ?
Pregunta 1.1: Pedido ?
unordered_
contenedor, de lo contrario use su contraparte ordenada tradicional.Pregunta 1.2: ¿ Clave separada ?
map
, de lo contrario use unset
Pregunta 1.3: ¿ Duplicados ?
multi
, de lo contrario no lo haga.Ejemplo:
Supongamos que tengo varias personas con una ID única asociada a ellas, y me gustaría recuperar los datos de una persona de su ID de la manera más simple posible.
Quiero una find
función, por lo tanto, un contenedor asociativo
1.1. No podría importarme menos el orden, por lo tanto, un unordered_
contenedor
1.2. Mi clave (ID) está separada del valor con el que está asociada, por lo tanto, unmap
1.3. La ID es única, por lo tanto, no debe introducirse ningún duplicado.
La respuesta final es: std::unordered_map<ID, PersonData>
.
Pregunta 2: ¿ Memoria estable ?
list
Pregunta 2.1: ¿Cuál ?
list
; a forward_list
solo es útil para una menor huella de memoria.Pregunta 3: ¿ Tamaño dinámico ?
{ ... }
sintaxis), entonces use un array
. Reemplaza la matriz C tradicional, pero con funciones convenientes.Pregunta 4: doble final ?
deque
, de lo contrario use a vector
.Notará que, por defecto, a menos que necesite un contenedor asociativo, su elección será a vector
. Resulta que también es la recomendación de Sutter y Stroustrup .
array
no requiere un tipo constructivo predeterminado; 2) elegir el multi
s no se trata tanto de que se permitan duplicados sino más bien de si mantenerlos importa (puede poner duplicados en no multi
contenedores, simplemente sucede que solo se conserva uno).
map.find(key)
es mucho más apetecible que std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));
sin embargo, por lo tanto, es importante, semánticamente, que find
sea una función miembro en lugar de una <algorithm>
. En cuanto a O (1) vs O (log n), no afecta la semántica; Eliminaré el "eficientemente" del ejemplo y lo reemplazaré con "fácilmente".
deque
tenía esta propiedad?
deque
los elementos son estables solo si presiona / pop en cualquier extremo; Si comienza a insertar / borrar en el medio, se barajan hasta N / 2 elementos para llenar el espacio creado.
Me gusta la respuesta de Matthieu, pero voy a reformular el diagrama de flujo de esta manera:
Por defecto, si necesita un contenedor de cosas, úselo std::vector
. Por lo tanto, cualquier otro contenedor solo se justifica proporcionando alguna alternativa de funcionalidad a std::vector
.
std::vector
requiere que su contenido sea movible, ya que necesita poder barajar los elementos. Esta no es una carga terrible para colocar en los contenidos (tenga en cuenta que no se requieren constructores predeterminados , gracias, emplace
etc.). Sin embargo, la mayoría de los otros contenedores no requieren ningún constructor particular (de nuevo, gracias a emplace
). Entonces, si tiene un objeto en el que no puede implementar absolutamente un constructor de movimiento, tendrá que elegir otra cosa.
A std::deque
sería el reemplazo general, que tiene muchas de las propiedades de std::vector
, pero solo puede insertar en cualquiera de los extremos de la deque. Las inserciones en el medio requieren movimiento. A std::list
no impone ningún requisito sobre su contenido.
std::vector<bool>
no es. Bueno, es estándar. Pero no es una vector
en el sentido habitual, ya que las operaciones que std::vector
normalmente permiten están prohibidas. Y ciertamente no contiene bool
s .
Por lo tanto, si necesita un vector
comportamiento real de un contenedor de bool
s, no lo obtendrá std::vector<bool>
. Por lo tanto, tendrá que pagar con a std::deque<bool>
.
Si necesita encontrar elementos en un contenedor, y la etiqueta de búsqueda no puede ser solo un índice, entonces es posible que deba abandonar std::vector
a favor de set
y map
. Tenga en cuenta la palabra clave " puede "; un ordenado std::vector
es a veces una alternativa razonable. O Boost.Container's flat_set/map
, que implementa un ordenado std::vector
.
Ahora hay cuatro variaciones de estos, cada uno con sus propias necesidades.
map
cuando la etiqueta de búsqueda no sea lo mismo que el elemento que está buscando. De lo contrario, utilice a set
.unordered
cuando tenga muchos elementos en el contenedor y el rendimiento de la búsqueda sea absolutamente necesario O(1)
, en lugar de hacerlo O(logn)
.multi
si necesita varios elementos para tener la misma etiqueta de búsqueda.Si necesita un contenedor de elementos para ordenar siempre en función de una operación de comparación particular, puede usar a set
. O bien, multi_set
si necesita varios elementos para tener el mismo valor.
O puede usar un ordenado std::vector
, pero tendrá que mantenerlo ordenado.
Cuando los iteradores y las referencias se invalidan, a veces es una preocupación. Si necesita una lista de elementos, de modo que tenga iteradores / punteros a esos elementos en varios otros lugares, entonces std::vector
el enfoque de invalidación puede no ser apropiado. Cualquier operación de inserción puede causar invalidación, dependiendo del tamaño y la capacidad actual.
std::list
ofrece una garantía firme: un iterador y sus referencias / punteros asociados solo se invalidan cuando el artículo en sí se retira del contenedor. std::forward_list
está ahí si la memoria es una preocupación seria.
Si esa es una garantía demasiado fuerte, std::deque
ofrece una garantía más débil pero útil. La invalidación es el resultado de las inserciones en el medio, pero las inserciones en la cabeza o la cola solo causan la invalidación de los iteradores , no los punteros / referencias a los elementos en el contenedor.
std::vector
solo proporciona una inserción económica al final (e incluso entonces, se vuelve costoso si se sopla la capacidad).
std::list
es costoso en términos de rendimiento (cada elemento recién insertado cuesta una asignación de memoria), pero es consistente . También ofrece la capacidad ocasionalmente indispensable de mezclar elementos sin prácticamente ningún costo de rendimiento, así como de intercambiar artículos con otros std::list
contenedores del mismo tipo sin pérdida de rendimiento. Si tiene que mezclar las cosas mucho , el uso std::list
.
std::deque
proporciona inserción / extracción en tiempo constante en la cabeza y la cola, pero la inserción en el medio puede ser bastante costosa. Por lo tanto, si necesita agregar / quitar elementos del frente y de la parte posterior, std::deque
podría ser lo que necesita.
Cabe señalar que, gracias a la semántica de movimiento, el std::vector
rendimiento de inserción puede no ser tan malo como solía ser. Algunas implementaciones implementaron una forma de mover copia de elementos basada en la semántica (la llamada "swaptimization"), pero ahora que mover es parte del lenguaje, es obligatorio por el estándar.
std::array
es un buen contenedor si desea la menor cantidad posible de asignaciones dinámicas. Es solo una envoltura alrededor de una matriz C; Esto significa que su tamaño debe conocerse en tiempo de compilación . Si puedes vivir con eso, entonces úsalo std::array
.
Dicho esto, usar std::vector
y usar reserve
un tamaño funcionaría igual de bien para un acotado std::vector
. De esta forma, el tamaño real puede variar y solo se obtiene una asignación de memoria (a menos que se reduzca la capacidad).
std::sort
que también std::inplace_merge
es interesante colocar fácilmente nuevos elementos (en lugar de una llamada std::lower_bound
+ std::vector::insert
). Es bueno aprender flat_set
y flat_map
!
vector<bool>
es vector<char>
.
std::allocator<T>
no admite esa alineación (y no sé por qué no lo haría), siempre puede usar su propio asignador personalizado.
std::vector::resize
tiene una sobrecarga que no toma un valor (solo toma el nuevo tamaño; cualquier elemento nuevo se construirá por defecto en el lugar). Además, ¿por qué los compiladores no pueden alinear correctamente los parámetros de valor, incluso cuando se declara que tienen esa alineación?
bitset
para bool si conoce el tamaño de antemano es.cppreference.com/w/cpp/utility/bitset
Aquí está la versión C ++ 11 del diagrama de flujo anterior. [publicado originalmente sin atribución a su autor original, Mikael Persson ]
Aquí hay un giro rápido, aunque probablemente necesite trabajo
Should the container let you manage the order of the elements?
Yes:
Will the container contain always exactly the same number of elements?
Yes:
Does the container need a fast move operator?
Yes: std::vector
No: std::array
No:
Do you absolutely need stable iterators? (be certain!)
Yes: boost::stable_vector (as a last case fallback, std::list)
No:
Do inserts happen only at the ends?
Yes: std::deque
No: std::vector
No:
Are keys associated with Values?
Yes:
Do the keys need to be sorted?
Yes:
Are there more than one value per key?
Yes: boost::flat_map (as a last case fallback, std::map)
No: boost::flat_multimap (as a last case fallback, std::map)
No:
Are there more than one value per key?
Yes: std::unordered_multimap
No: std::unordered_map
No:
Are elements read then removed in a certain order?
Yes:
Order is:
Ordered by element: std::priority_queue
First in First out: std::queue
First in Last out: std::stack
Other: Custom based on std::vector?????
No:
Should the elements be sorted by value?
Yes: boost::flat_set
No: std::vector
Puede notar que esto difiere enormemente de la versión C ++ 03, principalmente debido al hecho de que realmente no me gustan los nodos vinculados. Los contenedores de nodos vinculados generalmente pueden verse superados por un contenedor no vinculado, excepto en algunas situaciones poco frecuentes. Si no sabe cuáles son esas situaciones y tiene acceso a impulso, no use contenedores de nodos vinculados. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Esta lista se centra principalmente en contenedores pequeños y medianos, porque (A) es el 99.99% de lo que tratamos en código, y (B) Un gran número de elementos necesitan algoritmos personalizados, no contenedores diferentes.