mapa - std:: map example c++
En los mapas STL, ¿es mejor usar map:: insert que[]? (12)
Ahora en c ++ 11, creo que la mejor manera de insertar un par en un mapa STL es:
typedef std::map<int, std::string> MyMap;
MyMap map;
auto& result = map.emplace(3,"Hello");
El resultado será un par con:
El primer elemento (result.first), apunta al par insertado o apunta al par con esta clave si la clave ya existe.
Segundo elemento (result.second), verdadero si la inserción fue correcta o falsa, algo salió mal.
PD: Si no te importa el orden, puedes usar std :: unordered_map;)
¡Gracias!
Hace un tiempo, tuve una discusión con un colega sobre cómo insertar valores en los maps STL. Prefiero el map[key] = value;
porque se siente natural y es map.insert(std::make_pair(key, value))
leer, mientras que él prefiere map.insert(std::make_pair(key, value))
Solo le pregunté y ninguno de los dos puede recordar la razón por la cual el inserto es mejor, pero estoy seguro de que no fue solo una preferencia de estilo sino que hubo una razón técnica como la eficiencia. La maps simplemente dice "Estrictamente hablando, esta función miembro es innecesaria: existe solo por conveniencia".
¿Alguien puede decirme esa razón, o solo estoy soñando que hay una?
Aquí hay otro ejemplo, que muestra que el operator[]
sobrescribe el valor de la clave si existe, pero .insert
no sobrescribe el valor si existe.
void mapTest()
{
map<int,float> m;
for( int i = 0 ; i <= 2 ; i++ )
{
pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;
if( result.second )
printf( "%d=>value %f successfully inserted as brand new value/n", result.first->first, result.first->second ) ;
else
printf( "! The map already contained %d=>value %f, nothing changed/n", result.first->first, result.first->second ) ;
}
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f/n", iter->first, iter->second ) ;
/// now watch this..
m[5]=900.f ; //using operator[] OVERWRITES map values
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f/n", iter->first, iter->second ) ;
}
Cuando escribes
map[key] = value;
no hay forma de saber si reemplazó el value
por key
o si creó una nueva key
con value
.
map::insert()
solo creará:
using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
cout << "key " << key << " already exists "
<< " with value " << (res.first)->second << endl;
} else {
cout << "created key " << key << " with value " << value << endl;
}
Para la mayoría de mis aplicaciones, generalmente no me importa si estoy creando o reemplazando, así que uso el map[key] = value
más fácil de leer.
El hecho de que la función std :: map insert()
no sobrescriba el valor asociado con la clave nos permite escribir código de enumeración de objetos como este:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
dict.insert(make_pair(word, dict.size()));
}
Es un problema bastante común cuando necesitamos asignar diferentes objetos no únicos a algunos ID en el rango 0..N. Esos ID se pueden usar más adelante, por ejemplo, en algoritmos de grafos. La alternativa con el operator[]
parecería menos legible en mi opinión:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
size_t sz = dict.size();
if (!dict.count(word))
dict[word] = sz;
}
Este es un caso bastante restringido, pero a juzgar por los comentarios que recibí, creo que vale la pena destacar.
He visto personas en el pasado usar mapas en forma de
map< const key, const val> Map;
para evadir casos de sobreescritura accidental de valores, pero luego continúe escribiendo en algunos otros bits de código:
const_cast< T >Map[]=val;
Como lo recuerdo, su razón para hacer esto fue porque estaban seguros de que en estos ciertos fragmentos de código no iban a sobrescribir los valores del mapa; Por lo tanto, seguir adelante con el método más ''legible'' []
.
Nunca tuve problemas directos con el código que escribieron estas personas, pero hasta el día de hoy siento que los riesgos, por pequeños que sean, no deben tomarse cuando pueden evitarse fácilmente.
En los casos en los que se trata de valores de mapa que no deben sobrescribirse, use insert
. No hagas excepciones simplemente por legibilidad.
Los dos tienen diferentes semánticas cuando se trata de la clave que ya existe en el mapa. Así que no son realmente comparables directamente.
Pero la versión del operador [] requiere que el valor se construya por defecto y luego se asigne, por lo que si esto es más costoso, entonces copie la construcción, entonces será más costoso. A veces, la construcción predeterminada no tiene sentido, y entonces sería imposible usar la versión del operador [].
Otra cosa a tener en cuenta con std::map
:
myMap[nonExistingKey];
creará una nueva entrada en el mapa, con clave a nonExistingKey
inicializada a un valor predeterminado.
Esto me asustó muchísimo la primera vez que lo vi (mientras me golpeaba la cabeza contra un desagradable error heredado). No lo habría esperado. Para mí, eso parece una operación de obtención, y no esperaba el "efecto secundario". Prefiere map.find()
al obtener de su mapa.
Si el impacto de rendimiento del constructor predeterminado no es un problema, por favor, por el amor de Dios, vaya con la versión más legible.
:)
Si su aplicación es crítica para la velocidad, le aconsejaré usar el operador [] porque crea un total de 3 copias del objeto original, de las cuales 2 son objetos temporales y tarde o temprano se destruyen.
Pero en insert (), se crean 4 copias del objeto original, de las cuales 3 son objetos temporales (no necesariamente "temporales") y se destruyen.
Lo que significa tiempo adicional para: 1. Una asignación de memoria de objetos 2. Una llamada de constructor adicional 3. Una llamada de destructor adicional 4. Una desasignación de memoria de objetos
Si sus objetos son grandes, los constructores son típicos, los destructores liberan muchos recursos, los puntos anteriores cuentan aún más. En cuanto a la legibilidad, creo que ambos son lo suficientemente justos.
La misma pregunta vino a mi mente, pero no sobre legibilidad sino velocidad. Aquí hay un código de ejemplo a través del cual llegué a conocer el punto que mencioné.
class Sample
{
static int _noOfObjects;
int _objectNo;
public:
Sample() :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
}
Sample( const Sample& sample) :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
}
~Sample()
{
std::cout<<"Destroying object "<<_objectNo<<std::endl;
}
};
int Sample::_noOfObjects = 0;
int main(int argc, char* argv[])
{
Sample sample;
std::map<int,Sample> map;
map.insert( std::make_pair<int,Sample>( 1, sample) );
//map[1] = sample;
return 0;
}
Un gotcha con map :: insert () es que no reemplazará un valor si la clave ya existe en el mapa. He visto un código C ++ escrito por programadores de Java en el que esperaban que insert () se comporte de la misma manera que Map.put () en Java, donde se reemplazan los valores.
Una nota es que también puedes usar Boost.Assign :
using namespace std;
using namespace boost::assign; // bring ''map_list_of()'' into scope
void something()
{
map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}
insert
es mejor desde el punto de vista de la seguridad.
La expresión map[key] = value
es en realidad dos operaciones:
-
map[key]
- creando un elemento de mapa con valor predeterminado. -
= value
- copiando el valor en ese elemento.
Una excepción puede ocurrir en el segundo paso. Como resultado, la operación se realizará solo parcialmente (se agregó un nuevo elemento en el mapa, pero ese elemento no se inicializó con value
). La situación cuando una operación no está completa, pero se modifica el estado del sistema, se denomina operación con "efecto secundario".
insert
operación de insert
ofrece una gran garantía, significa que no tiene efectos secundarios ( https://en.wikipedia.org/wiki/Exception_safety ). insert
está completamente terminada o deja el mapa en estado no modificado.
http://www.cplusplus.com/reference/map/map/insert/ :
Si se va a insertar un solo elemento, no hay cambios en el contenedor en caso de excepción (garantía sólida).