c++ - relacion - semantica y sintaxis definicion
¿Razón de la semántica de inserción de mapa estándar de C++? (5)
Desde insert()
no espera que se toquen los objetos existentes en el contenedor. Por eso es que simplemente no los toca.
Estoy un poco confundido por la semántica de std::map::insert
. Quiero decir, no me quejo: el estándar es el estándar y la API es la forma en que es. Todavía,
insert
voluntad
la operación de inserción verifica para cada elemento insertado si ya existe otro elemento en el contenedor con el mismo valor clave, si es así, el elemento no se inserta y su valor asignado no se modifica de ninguna manera.
Y - solo en su pair<iterator,bool> insert ( const value_type& x );
versiones de un solo argumento pair<iterator,bool> insert ( const value_type& x );
incluso le dirá si incluso insertó el valor (nuevo, posiblemente diferente) a la (s) clave (s). Según tengo entendido, las versiones del iterador ignorarán silenciosamente las inserciones si la clave ya existe.
Para mí, esto es simplemente contraintuitivo, habría esperado que la parte de valor se sobrescribiera y la parte de valor anterior se descartara en la inserción. Obviamente, los diseñadores de la STL pensaron de manera diferente: ¿ alguien conoce la razón (histórica) o puede dar una explicación detallada de cómo la semántica existente tiene (más) sentido?
Por ejemplo:
Hay algunas formas básicas de implementar la inserción en un mapa de una sola tecla como std::map
:
- insertar, reemplazar si ya existe
- inserta, ignora si ya existe ( este es el comportamiento de std :: map )
- insertar, lanzar error si ya existe
- Insertar, UB si ya existe
¡Ahora estoy tratando de entender por qué insert_or_ignore
tiene más sentido que insert_or_replace
(o insert_or_error
)!
Busqué en mi copia de TC++PL (desafortunadamente solo tengo la edición en alemán), y curiosamente, Stroustrup escribe en el capítulo 17.4.1.7 (lista de operaciones para el mapa ): (lo siento, traducción aproximada del alemán)
(...) Normalmente, a uno no le importa si una clave (sic!) Está recién insertada o ya existió antes de la llamada a
insert()
(...)
Lo cual, me parece, solo sería válido para el conjunto , y no para el mapa , porque para un mapa, hace bastante diferencia si el valor proporcionado se insertó o el anterior permanece en el mapa. (Obviamente, no importa la clave, ya que esa es equivalente).
Nota: conozco el operator[]
y conozco el artículo 24 de Effective STL y la función efficientAddOrUpdate
adición y actualización propuesta. Solo tengo curiosidad por una explicación racional de la semántica del insert
porque personalmente los encuentro contra intuitivos.
El método de inserción no es lo que está buscando, suena como ... El método de inserción está hecho para hacer lo que su nombre implica ... insertar valores. Estoy de acuerdo en que la capacidad de crear un valor si uno no está ya presente, y reemplazar el que está allí, de lo contrario es importante en algunas situaciones, pero en otras simplemente preferiría no manejar las excepciones, los valores de retorno, etc. desea hacer una inserción solo si el valor no está ya presente.
Parece que el método que está buscando (como lo indica BoBTFish anteriormente) es probablemente el operador []
. Solo utilízalo así:
myMap["key"] = "value";
Esto irá a través de su mapa y encontrará la clave "clave", y reemplazará el valor correspondiente con "valor". Si la clave no está allí, la creará. Ambos métodos son muy útiles en diferentes situaciones, y me he encontrado usando ambos solo dependiendo de lo que necesito.
No conozco una justificación oficial, pero observaría la dualidad con el operator[]
.
Parece obvio que a uno le gustaría los dos sabores de inserción:
- puramente aditivo
- aditivo / destructivo
Si vemos un map
como una representación dispersa de una matriz, entonces la presencia del operator[]
tiene sentido. No sé si existían diccionarios preexistentes y dictó esta sintaxis (quizás, por qué no).
Además, todos los contenedores STL tienen varias sobrecargas de insert
, y esta similitud de interfaces es lo que permite la Programación Genérica.
Por lo tanto, tenemos al menos dos contendientes para la API: operator[]
e insert
.
Ahora, en C ++, si lees:
array[4] = 5;
es natural que el contenido de la celda en el índice 4
se haya actualizado de manera destructiva. Como tal, es natural que map::operator[]
devuelva una referencia para permitir esta actualización destructiva.
En este punto, ahora también necesitamos una versión puramente aditiva, y tenemos este método de insert
por ahí. Por qué no ?
Por supuesto, se podría haber dado insert
la misma semántica que el operator[]
y luego seguir adelante e implementar un método insert_or_ignore
en la parte superior . Sin embargo, esto habría sido más trabajo.
Por lo tanto, aunque estoy de acuerdo en que puede ser sorprendente, creo que mi razonamiento no es demasiado erróneo y puede ser una explicación probable de las circunstancias que nos llevan aquí :)
Respecto a las alternativas que propuso:
- Insertar, UB si ya existe
Afortunadamente, no lo es!
- insertar, lanzar error si ya existe
Solo Java (y sus derivados) es excepcionalmente loco. C ++ se concibió en una época en que se usaban excepciones para circunstancias excepcionales.
- insertar, reemplazar si ya existe
- inserta, ignora si ya existe (este es el comportamiento de std :: map)
Estamos de acuerdo en que la elección fue entre uno de esos. Tenga en cuenta que aunque el map
eligió la segunda opción, no ignora por completo el hecho de que el elemento ya existía, al menos en la versión de un solo elemento, ya que le advierte que no se insertó el elemento.
No pretendo conocer el fundamento original de la decisión, pero no es demasiado difícil inventarlo. Yo creo que ;-)
El comportamiento actual de "insertar o ignorar" hace que sea muy fácil implementar los otros dos, al menos para aquellos de nosotros que no estamos por encima de la creación y el uso de funciones que no sean miembros para complementar la funcionalidad de la biblioteca estándar ("no es OOP- ¡Ya basta! ").
Ejemplo (escrito en el lugar, por lo que los errores pueden estar presentes):
template<typename Map>
void insert_or_update(Map& map, typename Map::value_type const& x)
{
std::pair<typename Map::iterator, bool> result = map.insert(x);
if (!result.second)
result.first->second = x.second; // or throw an exception (consider using
// a different function name, though)
}
Tenga en cuenta que, tal como está, la función anterior no difiere mucho del operator[]
; sí, evita la inicialización predeterminada, pero al mismo tiempo (porque soy perezoso) no puede capitalizar la semántica de movimientos que está haciendo. STL hasta la fecha probablemente ya sea compatible con el operator[]
.
De todos modos, cualquier otro comportamiento de inserción para el map
hubiera hecho más tedioso implementar los demás, ya que map::find
solo devuelve un centinela final si la clave no está ya en el mapa. Por supuesto, con la ayuda de <algorithm>
(y especialmente en lower_bound
) aún sería posible escribir funciones accesorias ejecutantes sin ahogarlas en detalles de implementación y construcciones genéricas desagradables como bucles ;-).
pair<iterator,bool>
<- ¿la parte bool no dice si la inserción tuvo éxito o no?
Solo puede actualizar la parte del valor del iterador devuelto si la parte bool es falsa para actualizar el elemento existente con la misma clave.