example c++ stl insert stdmap std-pair

c++ - example - std map emplace



Forma preferida/idiomática para insertar en un mapa (8)

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 en la que no he pensado?)


A partir de C ++ 11, tienes dos opciones adicionales principales. Primero, puede usar insert() con la sintaxis de inicialización de la 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 notado, esto tiene varias ventajas sobre las otras formas:

  • El enfoque del operator[] requiere que el tipo mapeado sea asignable, lo que no siempre es el caso.
  • El enfoque del operator[] puede sobrescribir los elementos existentes y no le permite saber si esto sucedió.
  • Las otras formas de insert que enumera implican una conversión de tipo implícita, que puede ralentizar su código.

El mayor inconveniente es que este formulario solía requerir que la clave y el valor se puedan copiar, por lo que no funcionaría, por ejemplo, con un mapa con valores unique_ptr . Eso se ha corregido en el estándar, pero es posible que la corrección no haya llegado a su implementación de biblioteca estándar.

En segundo lugar, puede usar el método emplace() :

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 ligeramente más eficiente (aunque un compilador decente debería optimizar la diferencia). El único inconveniente importante es que puede sorprender un poco a sus lectores, ya que los métodos de emplace no suelen utilizarse de esa manera.


En primer lugar, el operator[] y las funciones de miembro de insert no son funcionalmente equivalentes:

  • El operator[] buscará la clave, insertará un valor construido predeterminado si no se encuentra y devolverá una referencia a la que asigne un valor. Obviamente, esto puede ser ineficiente si el mapped_type puede beneficiarse de ser inicializado directamente en lugar de predeterminado construido y asignado. Este método también hace que sea imposible determinar si realmente se ha realizado una inserción o si solo se sobrescribió el valor de una clave previamente insertada.
  • La función miembro de insert no tendrá efecto si la clave ya está presente en el mapa y, aunque a menudo se olvida, devuelve un std::pair<iterator, bool> que puede ser de interés (sobre todo para determinar si la inserción tiene realmente ha hecho).

De todas las posibilidades enumeradas para insert llamadas, las tres son casi equivalentes. Como recordatorio, echemos un vistazo a la firma de insert en el estándar:

typedef pair<const Key, T> value_type; /* ... */ pair<iterator, bool> insert(const value_type& x);

Entonces, ¿cómo son las tres llamadas diferentes?

  • std::make_pair basa en la deducción del argumento de la plantilla y podría (y en este caso) producir algo de un tipo diferente al valor value_type del mapa, lo que requerirá una llamada adicional al constructor de la plantilla std::pair para convertir a value_type (es decir, agregar const a first_type )
  • std::pair<int, int> también requerirá una llamada adicional al constructor de la plantilla de std::pair para convertir el parámetro a value_type (es decir, agregar const a first_type )
  • std::map<int, int>::value_type deja absolutamente lugar a dudas ya que es directamente el tipo de parámetro esperado por la función miembro de insert .

Al final, evitaría usar el operator[] cuando el objetivo es insertar, a menos que no haya un costo adicional en la construcción por defecto y la asignación de mapped_type , y que no me preocupe determinar si una nueva clave se ha insertado efectivamente. Cuando se utiliza insert , la construcción de un value_type es probablemente el camino a seguir.


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 que insert() es más eficiente para agregar valores.

El fragmento citado de Effective STL: 50 Formas específicas para mejorar el uso de la biblioteca de plantillas estándar por Scott Meyers, artículo 24 podría ayudar.

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)); } }

Puede decidir elegir una versión libre de programación genérica de esto, y el punto es que creo que este paradigma (diferenciando ''agregar'' y ''actualizar'') es extremadamente útil.


He estado haciendo algunas comparaciones de tiempo entre las versiones mencionadas anteriormente:

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 (ejecuté 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 diferentes versiones de inserción se pueden descuidar (¡aunque no se realizó una prueba de hipótesis)!

El map_W_3[it] = Widget(2.0); la versión tarda aproximadamente un 10-15% más de tiempo en este ejemplo debido a una inicialización con el constructor predeterminado para Widget.


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 tecla, sobrescribiendo el valor que tenga esa clave. 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 haga 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 typedef para std::pair<const int, int> , y std::make_pair() obviamente produce un std::pair<> través de la magia de deducción de plantillas. El resultado final, sin embargo, debería ser el mismo para las versiones 2, 3 y 4.

¿Cuál usaría? Personalmente prefiero la versión 1; es conciso y "natural". Por supuesto, si su comportamiento de sobreescritura no es deseado, entonces preferiría la versión 4, ya que requiere menos tipeo que las versiones 2 y 3. No sé si existe una única forma de hecho 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());


Si desea sobrescribir el elemento con la clave 0

function[0] = 42;

De otra manera:

function.insert(std::make_pair(0, 42));


Si quiere insertar un elemento en std :: map - use la función insert (), y si quiere encontrar un elemento (por clave) y asignarle algo - use el operador [].

Para simplificar, inserte 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 );


Solo cambio el problema un poco (mapa de cadenas) para mostrar otro interés de inserción:

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!