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?)

Author: Tshepang, 2010-11-26

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 el mapped_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 un std::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 al value_type real del mapa, lo que requerirá una llamada adicional a std::pair constructor de plantilla para convertir a value_type (es decir : agregando const a first_type)
  • std::pair<int, int> también requerirá una llamada adicional al constructor de plantilla de std::pair para convertir el parámetro a value_type (ie : añadiendo const a first_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 miembro insert.

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.

 68
Author: icecrime,
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.

 74
Author: Geoff Romer,
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());
 7
Author: In silico,
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.

 3
Author: user3116431,
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));
 1
Author: Viktor Sehr,
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 );
 1
Author: Denis Shevchenko,
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.

 1
Author: galactica,
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 !

 0
Author: jo_,
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