He identificado cuatro formas diferentes de insertar elementos en un std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
¿Cuál de esas es la forma preferida / idiomática? (¿Y hay otra forma en la que no he pensado?)
Respuestas:
En primer lugar,
operator[]
yinsert
funciones miembro no son funcionalmente equivalentes:operator[]
va a buscar la llave, inserte un defecto construida valor si no se encuentra, y devolver una referencia a la que se asigna un valor. Obviamente, esto puede ser ineficaz simapped_type
puede beneficiarse de la inicialización directa en lugar de la construcción y asignación predeterminada. Este método también hace que sea imposible determinar si se ha realizado una inserción o si solo ha sobrescrito el valor de una clave insertada previamenteinsert
función miembro no tendrá ningún efecto si la clave ya está presente en el mapa y, aunque a menudo se olvida, devuelve unastd::pair<iterator, bool>
que puede ser de interés (sobre todo para determinar si la inserción se ha realizado realmente).De todas las posibilidades enumeradas para llamar
insert
, las tres son casi equivalentes. Como recordatorio, echemos un vistazo a lainsert
firma en el estándar:Entonces, ¿en qué se diferencian las tres llamadas?
std::make_pair
se basa en la deducción del argumento de la plantilla y podría (y en este caso lo hará ) producir algo de un tipo diferente al realvalue_type
del mapa, lo que requerirá una llamada adicional alstd::pair
constructor de la plantilla para convertir avalue_type
(es decir: agregarconst
afirst_type
)std::pair<int, int>
también requerirá una llamada adicional al constructor de la plantillastd::pair
para convertir el parámetro avalue_type
(es decir: agregarconst
afirst_type
)std::map<int, int>::value_type
No deja lugar a dudas, ya que es directamente el tipo de parámetro esperado por lainsert
función miembro.Al final, evitaría usar
operator[]
cuando el objetivo es insertar, a menos que no haya un costo adicional en la construcción y asignación predeterminadamapped_type
, y que no me importe determinar si una nueva clave se insertó efectivamente. Cuando se usainsert
, construir unvalue_type
es probablemente el camino a seguir.fuente
A partir de C ++ 11, tiene dos opciones adicionales importantes. Primero, puede usar
insert()
con la sintaxis de inicialización de lista:Esto es funcionalmente equivalente a
pero mucho más conciso y legible. Como han señalado otras respuestas, esto tiene varias ventajas sobre las otras formas:
operator[]
enfoque requiere que el tipo mapeado sea asignable, lo que no siempre es el caso.operator[]
enfoque puede sobrescribir elementos existentes y no le da forma de saber si esto ha sucedido.insert
que enumera implican una conversión de tipo implícita, lo que puede ralentizar su código.El mayor inconveniente es que este formulario solía requerir que la clave y el valor fueran copiables, por lo que no funcionaría, por ejemplo, con un mapa con
unique_ptr
valores. Eso se ha corregido en el estándar, pero es posible que aún no haya llegado a la implementación de su biblioteca estándar.En segundo lugar, puede utilizar el
emplace()
método:Esto es más conciso que cualquiera de las formas de
insert()
, funciona bien con tipos de solo movimiento comounique_ptr
, y teóricamente puede ser un poco más eficiente (aunque un compilador decente debería optimizar la diferencia). El único gran inconveniente es que puede sorprender un poco a sus lectores, ya que losemplace
métodos no se suelen utilizar de esa forma.fuente
La primera versión:
puede o no puede insertar el valor 42 en el mapa. Si la clave
0
existe, asignará 42 a esa clave, sobrescribiendo cualquier valor que tuviera esa clave. De lo contrario, inserta el par clave / valor.Las funciones de inserción:
por otro lado, no hagas nada si la clave
0
ya existe en el mapa. Si la clave no existe, inserta el par clave / valor.Las tres funciones de inserción son casi idénticas.
std::map<int, int>::value_type
es eltypedef
parastd::pair<const int, int>
, ystd::make_pair()
obviamente produce unastd::pair<>
deducción mágica a través de la plantilla. Sin embargo, el resultado final debería ser el mismo para las versiones 2, 3 y 4.¿Cuál usaría? Yo personalmente prefiero la versión 1; es conciso y "natural". Por supuesto, si no se desea su comportamiento de sobrescritura, preferiría la versión 4, ya que requiere menos escritura que las versiones 2 y 3. No sé si hay una única forma de facto de insertar pares clave / valor en un
std::map
.Otra forma de insertar valores en un mapa a través de uno de sus constructores:
fuente
Si desea sobrescribir el elemento con la tecla 0
De otra manera:
fuente
Dado que C ++ 17
std::map
ofrece dos nuevos métodos de inserción:insert_or_assign()
ytry_emplace()
, como también se menciona en el comentario de sp2danny .insert_or_assign()
Básicamente,
insert_or_assign()
es una versión "mejorada" deoperator[]
. A diferencia deoperator[]
,insert_or_assign()
no requiere que el tipo de valor del mapa sea construible por defecto. Por ejemplo, el siguiente código no se compila porqueMyClass
no tiene un constructor predeterminado:Sin embargo, si reemplaza
myMap[0] = MyClass(1);
por la siguiente línea, entonces el código se compila y la inserción se realiza según lo previsto:Además, al igual que
insert()
,insert_or_assign()
devuelve apair<iterator, bool>
. El valor booleano estrue
si se produjo una inserción yfalse
si se realizó una asignación. El iterador apunta al elemento que se insertó o actualizó.try_emplace()
Similar a lo anterior,
try_emplace()
es una "mejora" deemplace()
. A diferencia deemplace()
,try_emplace()
no modifica sus argumentos si la inserción falla debido a una clave que ya existe en el mapa. Por ejemplo, el siguiente código intenta colocar un elemento con una clave que ya está almacenada en el mapa (ver *):Salida (al menos para VS2017 y Coliru):
Como puede ver,
pMyObj
ya no apunta al objeto original. Sin embargo, si lo reemplazaauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
por el siguiente código, la salida se verá diferente, porquepMyObj
permanece sin cambios:Salida:
Código en Coliru
Tenga en cuenta: traté de que mis explicaciones fueran lo más breves y simples posible para encajarlas en esta respuesta. Para una descripción más precisa y completa, recomiendo leer este artículo sobre Fluent C ++ .
fuente
He estado realizando comparaciones de tiempo entre las versiones mencionadas anteriormente:
Resulta que las diferencias de tiempo entre las versiones de inserción son mínimas.
Esto da respectivamente para las versiones (ejecuté el archivo 3 veces, de ahí las 3 diferencias de tiempo consecutivas para cada una):
2198 ms, 2078 ms, 2072 ms
2290 ms, 2037 ms, 2046 ms
2592 ms, 2278 ms, 2296 ms
2234 ms, 2031 ms, 2027 ms
Por lo tanto, los resultados entre diferentes versiones de insertos pueden ignorarse (aunque no realicé una prueba de hipótesis).
La
map_W_3[it] = Widget(2.0);
versión toma aproximadamente un 10-15% más de tiempo para este ejemplo debido a una inicialización con el constructor predeterminado para Widget.fuente
En resumen, el
[]
operador es más eficiente para actualizar valores porque implica llamar al constructor predeterminado del tipo de valor y luego asignarle un nuevo valor, mientras queinsert()
es más eficiente para agregar valores.El fragmento citado de Effective STL: 50 formas específicas de mejorar el uso de la biblioteca de plantillas estándar de Scott Meyers, artículo 24, podría ayudar.
Puede decidir elegir una versión genérica sin programación de esto, pero el punto es que encuentro este paradigma (diferenciando 'agregar' y 'actualizar') extremadamente útil.
fuente
Si desea insertar un elemento en std :: map, use la función insert (), y si desea encontrar el elemento (por clave) y asignarle algo, use el operador [].
Para simplificar la inserción, use la biblioteca boost :: assign, así:
fuente
Solo cambio un poco el problema (mapa de cadenas) para mostrar otro interés de inserción:
el hecho de que el compilador no muestra ningún error en "rancking [1] = 42;" puede tener un impacto devastador!
fuente
std::string::operator=(char)
existe, pero muestran un error para el segundo porque el constructorstd::string::string(char)
no existe. No debería producir un error porque C ++ siempre interpreta libremente cualquier literal de estilo entero comochar
, por lo que esto no es un error del compilador, sino un error del programador. Básicamente, solo estoy diciendo que si eso introduce o no un error en su código es algo que debe tener en cuenta por sí mismo. Por cierto, puede imprimirrancking[0]
y se generará un compilador que use ASCII*
, que es(char)(42)
.