Forma preferida / idiomática de insertar en un mapa
He identificado cuatro formas diferentes de insertar 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 manera que no he pensado?)
8 answers
En primer lugar, operator[]
y insert
las funciones miembro no son funcionalmente equivalentes:
- El
operator[]
buscará la clave, insertará un valor default constructed si no se encuentra, y devolverá una referencia a la que le asigne un valor. Obviamente, esto puede ser ineficiente si elmapped_type
puede beneficiarse de ser inicializado directamente en lugar de construido y asignado por defecto. Este método también hace imposible determinar si una inserción efectivamente ha tenido lugar o si solo ha sobrescrito el valor de una clave insertada previamente - La función miembro
insert
no tendrá ningún efecto si la clave ya está presente en el mapa y, aunque a menudo se olvida, devuelve unstd::pair<iterator, bool>
que puede ser de interés (sobre todo para determinar si la inserción se ha hecho realmente).
De todas las posibilidades listadas para llamar a insert
, las tres son casi equivalentes. Como recordatorio, echemos un vistazo a insert
firma en el estándar :
typedef pair<const Key, T> value_type;
/* ... */
pair<iterator, bool> insert(const value_type& x);
Entonces, ¿cómo son diferentes las tres llamadas ?
-
std::make_pair
se basa en la deducción de argumentos de plantilla y podría (y en este caso producirá) producir algo de un tipo diferente alvalue_type
real del mapa, lo que requerirá una llamada adicional astd::pair
constructor de plantilla para convertir avalue_type
(es decir : agregandoconst
afirst_type
) -
std::pair<int, int>
también requerirá una llamada adicional al constructor de plantilla destd::pair
para convertir el parámetro avalue_type
(ie : añadiendoconst
afirst_type
) -
std::map<int, int>::value_type
no deja lugar a dudas, ya que es directamente el tipo de parámetro esperado por la función miembroinsert
.
Al final, evitaría usar operator[]
cuando el objetivo es insertar, a menos que no haya un costo adicional por defecto: construir y asignar el mapped_type
, y que no me importe determinar si una nueva clave se ha insertado efectivamente. Cuando se usa insert
, construir un value_type
es probablemente la forma ir.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-26 18:37:38
A partir de C++11, tiene dos opciones adicionales principales. Primero, puede usar insert()
con la sintaxis de inicialización de lista:
function.insert({0, 42});
Esto es funcionalmente equivalente a
function.insert(std::map<int, int>::value_type(0, 42));
Pero mucho más conciso y legible. Como otras respuestas han señalado, esto tiene varias ventajas sobre las otras formas:
- El enfoque
operator[]
requiere que el tipo asignado sea asignable, lo que no siempre es el caso. - El enfoque
operator[]
puede sobrescribir elementos existentes, y le da no hay forma de saber si esto ha sucedido. - Las otras formas de
insert
que lista 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 con, por ejemplo, un mapa con valores unique_ptr
. Eso se ha solucionado en el estándar, pero es posible que la solución aún no haya alcanzado la implementación de la biblioteca estándar.
En segundo lugar, puede utilizar el emplace()
método:
function.emplace(0, 42);
Esto es más conciso que cualquiera de las formas de insert()
, funciona bien con tipos de solo movimiento como unique_ptr
, y teóricamente puede ser un poco más eficiente (aunque un compilador decente debería optimizar la diferencia). El único inconveniente importante es que puede sorprender a sus lectores un poco, ya que emplace
los métodos no se usan generalmente de esa manera.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 18:13:23
La primera versión:
function[0] = 42; // version 1
Puede o no insertar el valor 42 en el mapa. Si la clave 0
existe, entonces asignará 42 a esa clave, sobrescribiendo cualquier valor que esa clave tuviera. De lo contrario, inserta el par clave/valor.
Las funciones de inserción:
function.insert(std::map<int, int>::value_type(0, 42)); // version 2
function.insert(std::pair<int, int>(0, 42)); // version 3
function.insert(std::make_pair(0, 42)); // version 4
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 el typedef
para std::pair<const int, int>
, y std::make_pair()
obviamente produce un std::pair<>
a través de la magia de deducción de plantilla. El resultado final, sin embargo, debe ser el mismo para las versiones 2, 3 y 4.
¿Cuál usaría? Personalmente prefiero la versión 1; es concisa y "natural". Por supuesto, si no se desea su comportamiento de sobrescritura, entonces preferiría la versión 4, ya que requiere menos escritura que las versiones 2 y 3. No se si hay una única de facto forma de insertar pares clave / valor en una std::map
.
Otra forma de insertar valores en un mapa a través de uno de sus constructores:
std::map<int, int> quadratic_func;
quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;
std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-26 16:10:04
He estado haciendo algunas comparaciones de tiempo entre las versiones antes mencionadas:
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));
Resulta que las diferencias de tiempo entre las versiones de inserción son pequeñas.
#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
Widget() {
m_vec.resize(100);
for(unsigned long it = 0; it < 100;it++) {
m_vec[it] = 1.0;
}
}
Widget(double el) {
m_vec.resize(100);
for(unsigned long it = 0; it < 100;it++) {
m_vec[it] = el;
}
}
private:
std::vector<double> m_vec;
};
int main(int argc, char* argv[]) {
std::map<int,Widget> map_W;
ptime t1 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
}
ptime t2 = boost::posix_time::microsec_clock::local_time();
time_duration diff = t2 - t1;
std::cout << diff.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_2;
ptime t1_2 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_2.insert(std::make_pair(it,Widget(2.0)));
}
ptime t2_2 = boost::posix_time::microsec_clock::local_time();
time_duration diff_2 = t2_2 - t1_2;
std::cout << diff_2.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_3;
ptime t1_3 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_3[it] = Widget(2.0);
}
ptime t2_3 = boost::posix_time::microsec_clock::local_time();
time_duration diff_3 = t2_3 - t1_3;
std::cout << diff_3.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_0;
ptime t1_0 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
}
ptime t2_0 = boost::posix_time::microsec_clock::local_time();
time_duration diff_0 = t2_0 - t1_0;
std::cout << diff_0.total_milliseconds() << std::endl;
system("pause");
}
Esto da respectivamente para las versiones (corrí el archivo 3 veces, de ahí las 3 diferencias de tiempo consecutivas para cada una):
map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
2198 ms, 2078 ms, 2072 ms
map_W_2.insert(std::make_pair(it,Widget(2.0)));
2290 ms, 2037 ms, 2046 ms
map_W_3[it] = Widget(2.0);
2592 ms, 2278 ms, 2296 ms
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
2234 ms, 2031 ms, 2027 ms
Por lo tanto, los resultados entre las diferentes versiones de inserción se pueden descuidar (sin embargo, no realizó una prueba de hipótesis)!
La versión map_W_3[it] = Widget(2.0);
toma alrededor de 10-15% más tiempo para este ejemplo debido a una inicialización con el constructor predeterminado para Widget.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-13 15:58:27
Si desea sobrescribir el elemento con la tecla 0
function[0] = 42;
De lo contrario:
function.insert(std::make_pair(0, 42));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-26 15:53:12
Si desea insertar un elemento en std::map, use la función insert (), y si desea encontrar un elemento (por clave) y asignarle algo, use el operador[].
Para simplificar la inserción use boost:: assign library, así:
using namespace boost::assign;
// For inserting one element:
insert( function )( 0, 41 );
// For inserting several elements:
insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-17 21:32:26
En resumen, el operador []
es más eficiente para actualizar valores porque implica llamar al constructor predeterminado del tipo value y luego asignarle un nuevo valor, mientras que insert()
es más eficiente para agregar valores.
El fragmento citado de Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library por Scott Meyers, Item 24 might help.
template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
typename MapType::iterator lb = m.lower_bound(k);
if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
lb->second = v;
return lb;
} else {
typedef typename MapType::value_type MVT;
return m.insert(lb, MVT(k, v));
}
}
Usted puede decidir elegir una versión genérica libre de programación de esto, y el punto es que yo encuentra este paradigma (diferenciando 'añadir' y 'actualizar') extremadamente útil.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-29 22:06:19
Simplemente cambio un poco el problema (mapa de cadenas) para mostrar otro interés de insert:
std::map<int, std::string> rancking;
rancking[0] = 42; // << some compilers [gcc] show no error
rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error
El hecho de que el compilador no muestra ningún error en "rancking[1] = 42;" puede tener un impacto devastador !
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-15 15:48:40