2009-12-20 11 views
29

Tengo una tabla de símbolos implementada como std::map. Para el valor, no hay forma de construir legítimamente una instancia del tipo de valor a través de un constructor predeterminado. Sin embargo, si no proporciono un constructor predeterminado, obtengo un error de compilación y si hago que el constructor sea afirmativo, mi programa compila bien pero se bloquea dentro de map<K,V>::operator [] si intento usarlo para agregar un nuevo miembro.Usando std :: map <K,V> donde V no tiene un constructor predeterminado utilizable

¿Hay alguna manera de que pueda conseguir que C++ desaconseje map[k] como un valor l en el momento de la compilación (mientras lo permite como un valor r)?


Por cierto: Sé que puedo insertar en el mapa utilizando Map.insert(map<K,V>::value_type(k,v)).


Editar: varias personas han propuesto una solución que equivalen a alterar el tipo del valor de manera que el mapa puede construir uno sin llamar al constructor por defecto. Esto tiene exactamente el resultado opuesto de lo que quiero porque oculta el error hasta más adelante. Si estuviera dispuesto a tener eso, simplemente podría eliminar la afirmación del constructor. Lo que Quiere es hacer que el error suceda incluso antes; en tiempo de compilación Sin embargo, parece que no hay forma de distinguir entre los usos r-value y l-value de operator[], así que parece que lo que quiero no se puede hacer, así que tendré que prescindir de usarlo todo junto.

Respuesta

31

No se puede hacer que el compilador diferencie entre los dos usos del operador [], porque son la misma cosa. El operador [] devuelve una referencia, por lo que la versión de asignación solo está asignando a esa referencia.

Personalmente, nunca uso el operador [] para mapas para otra cosa que no sea el código de demo rápido y sucio. Use insert() y find() en su lugar. Tenga en cuenta que la make_pair() Función de insertar hace más fácil de usar:

m.insert(make_pair(k, v)); 

En C++ 11, también se puede hacer

m.emplace(k, v); 
m.emplace(piecewise_construct, make_tuple(k), make_tuple(the_constructor_arg_of_v)); 

incluso si el constructor de copia/movimiento no se suministra .

+0

Si tiene C++ 11 o superior, le recomiendo usar una lista de inicializadores: 'm.insert ({k, v});'. Utilice 'V map :: at (clave K)' para recuperar el valor, p. Ej. 'int val = m.at (" important_value ")' – CJxD

1

Es un poco feo, pero una forma de evitar esto es agregar una variable miembro que rastrea si una instancia es válida o no. Su constructor predeterminado marcará una instancia como no válida, pero todos sus otros constructores marcan la instancia como válida.

Asegúrese de que su operador de asignación transfiere correctamente la nueva variable miembro.

Modifique su destructor para ignorar las instancias no válidas.

Modifique todas sus otras funciones miembro para arrojar/error/afirmar cuando operan en una instancia no válida.

A continuación, puede utilizar su objeto en un mapa y siempre que solo utilice objetos que se construyeron correctamente, su código funcionará bien.

De nuevo, esto es una solución si desea utilizar el mapa STL y no desea utilizar insert y find en lugar de operator [].

+0

Todo lo que hace es retrasar el problema. Quiero hacer aparecer el probelma incluso antes. Da la casualidad de que no necesito un indicador como objeto seg-v predeterminado cuando intentas usarlo. – BCS

0

Cuando utiliza una anulación de operador en C++, es mejor mantenerse lo más cerca posible con la semántica del operador en el caso predeterminado. La semántica del valor predeterminado. operator [] es la sustitución de un miembro existente en una matriz. Parece que std :: map dobla las reglas un poco. Eso es desafortunado, porque lleva a este tipo de confusión.

Tenga en cuenta que la documentación (http://www.sgi.com/tech/stl/Map.html) para el operador [] en std :: map dice: "Devuelve una referencia al objeto asociado a una clave en particular. Si el mapa ya no contiene dicho objeto, operador [ ] inserta el objeto predeterminado data_type(). "

Le sugiero que trate el reemplazo y la inserción de manera diferente. Desafortunadamente, esto significa que necesita saber cuál es requerido. Eso puede significar primero hacer una búsqueda en el mapa. Si el rendimiento es un problema, es posible que deba encontrar una optimización donde pueda probar la membresía e insertar con una búsqueda.

5

Su V no tiene un constructor por defecto, por lo que no se puede esperar std::map<K,V>std::map<K,V>::operator[] para ser utilizable.

Un std::map<K, boost::optional<V> >hace tienen una mapped_type que es por defecto Urbanizable, y probablemente tiene la semántica que desee. Consulte la documentación Boost.Optional para obtener más información (será debe tener en cuenta).

+2

Está perfectamente bien utilizar tipos no construibles por defecto con std :: map; simplemente no puede usar el operador []. –

+0

Es cierto, gracias! Incorporado en el texto. – ariels

4

Si el tipo de valor no es predecible, entonces operator[] no funcionará para usted.

Lo que puede hacer, sin embargo, es proporcionar funciones gratuitas que obtengan y establezcan valores en un mapa para su comodidad.

Ej:

template <class K, class V> 
V& get(std::map<K, V>& m, const K& k) 
{ 
    typename std::map<K, V>::iterator it = m.find(k); 
    if (it != m.end()) { 
     return it->second; 
    } 
    throw std::range_error("Missing key"); 
} 

template <class K, class V> 
const V& get(const std::map<K, V>& m, const K& k) 
{ 
    typename std::map<K, V>::const_iterator it = m.find(k); 
    if (it != m.end()) { 
     return it->second; 
    } 
    throw std::range_error("Missing key"); 
} 

template <class K, class V> 
void set(std::map<K, V>& m, const K& k, const V& v) 
{ 
    std::pair<typename std::map<K, V>::iterator,bool> result = m.insert(std::make_pair(k, v)); 
    if (!result.second) { 
     result.first->second = v; 
    } 
} 

También puede considerar un captador como dict.get(key [, default]) en Python (que devuelve el valor por defecto proporcionada si la clave no está presente (pero que tiene un problema de usabilidad en el que el valor predeterminado siempre tiene que ser construido , incluso si usted sabe que la clave está en el mapa)

+0

re: el valor predeterminado siempre se está construyendo, eso es lo que delegados, evaluación diferida y lambdas son para :) – BCS

+0

En C++ 11, 'V map :: at (clave K)' funciona de maravilla. Ahorra obtener un iterador y hacer comprobaciones. – CJxD

0

puede especializar std :: map para su tipo de valor. No estoy diciendo que sea una buena idea, pero se puede hacer. Me especialicé scoped_ptr<FILE> ' s dtor a fclose en lugar de delete.

Algo así como:

template<class K, class Compare, class Allocator> 
my_value_type& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
{ 
    //... 
} 

Esto debería permitir que inserte el código que desee en el operador [] para su tipo. Desafortunadamente, no sé de ninguna manera en el C++ actual para devolver solo los valores r. En C++ 0x es posible que pueda utilizar:

template<class K, class Compare, class Allocator> 
my_value_type&& std::map<K,my_value_type,Compare,Allocator>::operator[](const K& k) 
{ 
    //... 
} 

Esto devolverá una referencia de valor R (& &).

1

No estoy seguro de por qué compila para usted, creo que el compilador debería haber capturado su constructor faltante.

lo que acerca del uso de

map<K,V*> 

en lugar de

map<K,V> ? 
+0

mejor que el mapa sería el mapa > –

+0

Sin el constructor, no se compila. En cuanto al uso de V *, sería una especie de contraproducente, ya que alejaría la detección de errores hasta incluso más tarde y estoy tratando de que ocurra antes. Lo que intento hacer es compilar el código para casos que nunca llamarán al constructor predeterminado y no compilarán para los casos que podrían/​​podrían llamarlo. – BCS

+0

lo que necesita sería generar código parcial, basado en lo que realmente necesita. No creo que ningún compilador admita esto. cuando se genera una plantilla, se crea todo el código, no solo los bits que utiliza. –

3

derivar una nueva clase de std::map<K,V> y crear su propia operator[]. Haz que devuelva una referencia constante, que no se puede usar como un valor l.

+0

¡Oh, 'const' que podría funcionar! – BCS

+4

std :: map no tiene destructor virtual, por lo que es una mala práctica derivar de él –

+2

@Jacek, siempre y cuando su clase derivada no introduzca ningún miembro nuevo de datos y su propio destructor esté vacío, es seguro. –

2

Use map<K,V>::at(). map<K,V>::operator [] intentará construir de forma predeterminada un elemento si la clave proporcionada no existe todavía.

+0

simple, limpio y funciona sin problemas – DomTomCat

Cuestiones relacionadas