geeksforgeeks - Idiomatic C++ para leer desde un mapa const
stl map (7)
Para las std::map<std::string, std::string> variables
, me gustaría hacer esto:
BOOST_CHECK_EQUAL(variables["a"], "b");
El único problema es que, en este contexto, las variables
son const
, por lo que el operator[]
no funcionará :(
Ahora, hay varias soluciones para esto; descartando la const
, usando variables.count("a") ? variables.find("a")->second : std::string()
variables.count("a") ? variables.find("a")->second : std::string()
o incluso haciendo una función envolviendo eso. Ninguno de estos me parece tan agradable como el operator[]
. ¿Que debería hacer? ¿Hay una forma estándar de hacer esto (bellamente)?
Editar: Solo para indicar la respuesta que ninguno de ustedes quiere dar: No, no hay una forma conveniente, hermosa y estándar de hacer esto en C ++. Tendré que implementar una función de soporte.
De hecho, el operador [] no es consistente en std :: map, ya que inserta automáticamente un par clave-valor en el mapa si no estuviera allí. (¡Oooh efectos secundarios!)
La forma correcta es usar map::find
y, si el iterador devuelto es válido ( != map.end()
), devolver el second
, como mostraste.
map<int, int> m;
m[1]=5; m[2]=6; // fill in some couples
...
map<int,int>::const_iterator it = m.find( 3 );
if( it != m.end() ) {
int value = it->second;
// ... do stuff with value
}
Puede agregar un map::operator[]( const key_type& key ) const
en una subclase del std :: map que está utilizando, y afirmar la clave que se va a encontrar, después de lo cual debe devolver it->second
.
Eliminar const es incorrecto, porque el operador [] en el mapa <> creará la entrada si no está presente con una cadena construida por defecto. Si el mapa está realmente en almacenamiento inmutable, fallará. Esto debe ser así porque el operador [] devuelve una referencia no constante para permitir la asignación. (por ejemplo, m [1] = 2)
Una función gratuita rápida para implementar la comparación:
template<typename CONT>
bool check_equal(const CONT& m, const typename CONT::key_type& k,
const typename CONT::mapped_type& v)
{
CONT::const_iterator i(m.find(k));
if (i == m.end()) return false;
return i->second == v;
}
Pensaré en azúcar sintáctico y actualizaré si pienso en algo.
...
El azúcar sintáctico inmediato implicaba una función gratuita que hace un mapa <> :: find () y devuelve una clase especial que ajusta map <> :: const_iterator, y luego ha sobrecargado el operador == () y el operador! = () Para permitir comparación con el tipo mapeado. Entonces puedes hacer algo como:
if (nonmutating_get(m, "key") == "value") { ... }
No estoy convencido de que sea mucho mejor que:
if (check_equal(m, "key", "value")) { ... }
Y ciertamente es mucho más complejo y lo que está sucediendo es mucho menos obvio.
El propósito del objeto que envuelve el iterador es dejar de tener objetos de datos construidos por defecto. Si no le importa, simplemente use la respuesta "obtener".
En respuesta al comentario sobre el hecho de que se prefiera a una comparación con la esperanza de encontrar algún uso futuro, tengo estos comentarios:
Diga lo que quiere decir: llamar a una función llamada "check_equal" deja en claro que está haciendo una comparación de igualdad sin crear objetos.
Recomiendo solo implementar funcionalidad una vez que lo necesite. Hacer algo antes de eso es a menudo un error.
Dependiendo de la situación, un constructor predeterminado puede tener efectos secundarios. Si está comparando, ¿por qué hacer algo extra?
El argumento SQL: NULL no es equivalente a una cadena vacía. ¿Es la ausencia de una clave de su contenedor realmente la misma que la clave que está presente en su contenedor con un valor construido por defecto?
Habiendo dicho todo eso, un objeto construido por defecto es equivalente a usar map <> :: operator [] en un contenedor no const. Y quizás tenga un requisito actual para una función get que devuelva un objeto construido por defecto; Sé que he tenido ese requisito en el pasado.
Un aspecto interesante, hay dos maneras de hacer el descubrimiento de tipo de plantilla en la implementación get que fue aceptada (la que obtiene el valor o devuelve un objeto construido por defecto). Uno, puedes hacer lo que fue aceptado y tener:
template <typename K, typename V>
V get1(const std::map<K, V>& theMap, const K const key)
{
std::map<K, V>::const_iterator iter(theMap.find(key));
return iter != theMap.end() ? iter->second : V();
}
o puede usar el tipo de mapa y obtener los tipos de eso:
template<typename T>
typename T::mapped_type
get2(const T& theMap, const typename T::key_type& key)
{
typename T::const_iterator itr = theMap.find(key);
return itr != theMap.end() ? itr->second : typename T::mapped_type();
}
La ventaja de esto es que el tipo de clave que se pasa no se reproduce en el descubrimiento de tipo y puede ser algo que se puede convertir implícitamente en una clave. Por ejemplo:
std::map<std::string, int> data;
get1(data, "hey"); // doesn''t compile because the key type is ambiguous
get2(data, "hey"); // just fine, a const char* can be converted to a string
find
es la forma idiomática. Rechazar const
es casi siempre una mala idea. Debería garantizar que no se realice ninguna operación de escritura. Si bien esto se puede esperar razonablemente de un acceso de lectura en un mapa, la especificación no dice nada al respecto.
Si sabes que el valor existe, puedes, por supuesto, renunciar a la prueba utilizando el count
(que es bastante ineficiente, de todos modos, ya que significa atravesar el mapa dos veces. Incluso si no sabes si el elemento existe, no lo usaría). Use lo siguiente en su lugar:
T const& item(map<TKey, T> const& m, TKey const& key, T const& def = T()) {
map<TKey, T>::const_iterator i = m.find(key);
return i == m.end() ? def : i->second;
}
/ EDITAR: Como Chris ha señalado correctamente, la construcción por defecto de objetos de tipo T
puede ser costosa, especialmente porque esto se hace incluso si este objeto no es realmente necesario (porque la entrada existe). Si este es el caso, no use el valor predeterminado para el argumento def
en el caso anterior.
template <typename K, typename V>
V get(std::map<K, V> const& map, K const& key)
{
std::map<K, V>::const_iterator iter(map.find(key));
return iter != map.end() ? iter->second : V();
}
Implementación mejorada basada en comentarios:
template <typename T>
typename T::mapped_type get(T const& map, typename T::key_type const& key)
{
typename T::const_iterator iter(map.find(key));
return iter != map.end() ? iter->second : typename T::mapped_type();
}
std::map<std::string, std::string>::const_iterator it( m.find("a") );
BOOST_CHECK_EQUAL(
( it == m.end() ? std::string("") : it->second ),
"b"
);
Eso no me parece tan malo ... Probablemente no escribiría una función para esto.
Siguiendo la idea de xtofl de especializar el contenedor del mapa. ¿Funcionará bien lo siguiente?
template <typename K,typename V>
struct Dictionary:public std::map<K,V>
{
const V& operator[] (const K& key) const
{
std::map<K,V>::const_iterator iter(this->find(key));
BOOST_VERIFY(iter!=this->end());
return iter->second;
}
};