2008-09-30 4 views
14

Para una std::map<std::string, std::string> variables, me gustaría hacer esto:idiomático C++ para la lectura de un mapa const

BOOST_CHECK_EQUAL(variables["a"], "b"); 

El único problema es que, en este contexto es variablesconst, por lo operator[] no funcionará :(

Ahora, hay varias soluciones a este;. desechar los const, utilizando variables.count("a") ? variables.find("a")->second : std::string() o incluso hacer una función de envolver que ninguno de estos me parecen ser tan agradable como operator[] ¿Qué debo hacer ¿existe una norma.? forma de hacerlo (bellamente)? ​​

Editar: Solo para indicar la respuesta que ninguno de ustedes quiere dar: No, no hay una manera conveniente, hermosa y estándar de hacer esto en C++. Tendré que implementar una función de soporte.

Respuesta

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

Mejora de la aplicación basado en los 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(); 
} 
+0

Voy por esta implementación, colocándola en una biblioteca de soporte para que no tengamos que volver a implementarla en todas partes :) Me encantaría que STL se centrara más en la buena y vieja conveniencia de la caja ... Gracias :) –

+0

¡Eche un vistazo a la solución de janm también! ¡su función de comparación no será verdadera para una clave que no está en el mapa en combinación con una comparación predeterminada! – xtofl

+0

BTW, la única razón por la que la función devuelve V, y no V const &, es porque si se devuelve el valor por defecto de V(), no queremos devolver una referencia colgante. Si está preparado para garantizar que el artículo exista, simplemente haga "return iter-> second;" (comportamiento indefinido si el artículo no existe). –

5

find es la forma idiomática. Casting 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 sabe que el valor existe, por supuesto, se puede renunciar a la prueba usando count (que es bastante ineficiente, de todos modos, ya que significa que atraviesa el mapa dos veces. Incluso si usted no sabe si existe el elemento I no utilizaría este uso el siguiente 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; 
} 

/EDIT: Como Chris ha señalado correctamente, default-construcción de objetos de tipo T podría ser costoso, sobre todo porque se hace esto, incluso si esto objeto no es realmente necesario (porque la entrada existe). Si este es el caso, no use la def valor de ault para el argumento def en el caso anterior.

+0

Ow! Eso significa construir por defecto una cadena para cada llamada, cuando 'default' (una palabra clave, por cierto) no se especifica. –

+0

Las construcciones predeterminadas no deberían ser demasiado costosas para la mayoría de los tipos. Si lo son, no use el argumento predeterminado. –

+0

Eso, o tienen versiones sobrecargadas, una con su versión (pero no un valor predeterminado) y otra con la mía. :-) –

1

De hecho, operator [] es un non-const one 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 está utilizando map::find y, si el iterador devuelto es válido (!= map.end()), devolviendo el second, como se notaba.

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 
} 

Usted podría añadir un map::operator[](const key_type& key) const en una subclase de la std :: mapa que está usando, y hacer valer la clave para ser encontrado, después de que se les devuelva la it->second.

11

Desechar const es incorrecto, ya que el operador [] en el mapa <> creará la entrada si no está presente con una cadena predeterminada construido. 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. (p.ej.m [1] = 2)

Una función libre rápido de 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; 
} 

Voy a pensar en el azúcar y la actualización sintáctica si pienso en algo.

...

El azúcar sintáctico inmediata involucrada una función gratuita que hace un mapa <> :: find() y devuelve una clase especial que envuelve mapa <> :: const_iterator, y después se ha sobrecargado el operador = =() y operator! =() para permitir la comparación con el tipo mapeado. Así que usted puede hacer algo como:

if (nonmutating_get(m, "key") == "value") { ... } 

No estoy convencido de que es mucho mejor que:

if (check_equal(m, "key", "value")) { ... } 

y sin duda es mucho más complejo y lo que está sucediendo es mucho menos evidente.

El objeto del envoltorio del iterador es dejar de tener objetos de datos construidos por defecto. Si no le importa, simplemente use la respuesta "obtener".

En respuesta al comentario acerca del conseguir prefiriéndose más de una comparación con la esperanza de encontrar algún uso futuro, tengo estos comentarios:

  • decir lo que quiere decir: llamar a una función llamada "check_equal" deja en claro que está haciendo una comparación de igualdad sin creación de objetos.

  • Recomiendo solo la implementación de funcionalidad cuando lo necesite. Hacer algo antes de eso es a menudo un error.

  • Dependiendo de la situación, un constructor predeterminado podría 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 esto, un objeto predeterminado construida es equivalente a usar mapa <> :: operador [] en un recipiente no const. Y tal vez 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.

+0

Esta es una buena idea, pero prefiero la implementación get (incluso con su debilidad) con la esperanza de que la función pueda ser utilizada fuera de las pruebas de igualdad. –

+0

janm: De hecho, me gusta su solución mejor. También estoy de acuerdo con el argumento NULL. Puede, por ejemplo, decir check_equal ("foo", "") y hacer que devuelva verdadero solo si el mapa ["foo"] existe y está vacío. get (map, "foo") == "" dice algo diferente. –

+0

Da buenos consejos en general para la codificación de C++, gracias. Realmente estoy buscando cómo leer desde un mapa de const que cómo comparar un valor con uno en un mapa const. Para la base de código en la que estoy trabajando, estoy seguro de que la función get será una buena adición. Gracias de nuevo, aunque :) –

5

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, puede hacer lo que fue aceptado y tienen:

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 utilizar el tipo de mapa y obtener los tipos fuera 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 la La 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 
+0

He editado mi respuesta, debido a su argumento, y a la de janm sobre ser agnóstica de contenedor.Por cierto, el resultado final es muy parecido al tuyo (escribí mi propia implementación, pero supongo que no hay tantas formas de escribirlo). :-PAG –

0
std::map<std::string, std::string>::const_iterator it(m.find("a")); 
BOOST_CHECK_EQUAL( 
        (it == m.end() ? std::string("") : it->second), 
        "b" 
       ); 

que no se ve tan mal a mí ... probablemente no escribir una función para esto.

0

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; 
    } 
}; 
Cuestiones relacionadas