2010-03-17 15 views
7

Esta pregunta podría ser más adecuada en relación con C++ en general, pero como estoy usando gcc en Linux ese es el contexto. Considere el siguiente programa:C++: ¿Por qué gcc prefiere no const sobre const al acceder al operador []?

#include <iostream> 
#include <map> 
#include <string> 

using namespace std; 

template <typename TKey, typename TValue> 
class Dictionary{ 
    public: 
    map<TKey, TValue> internal; 

    TValue & operator[](TKey const & key) 
    { 
     cout << "operator[] with key " << key << " called " << endl; 
     return internal[key]; 
    } 

    TValue const & operator[](TKey const & key) const 
    { 
     cout << "operator[] const with key " << key << " called " << endl; 
     return internal.at(key); 
    } 

}; 

int main(int argc, char* argv[]) 
{ 
    Dictionary<string, string> dict; 
    dict["1"] = "one"; 
    cout << "first one: " << dict["1"] << endl; 

    return 0; 
} 

Al ejecutar el programa, la salida es:

operator[] with key 1 called 
    operator[] with key 1 called 
    first one: one 

Lo que me gustaría es tener el compilador elegir el método operator[]const lugar en la segunda llamada. La razón es que sin haber usado dict ["1"] antes, la llamada a operator[] hace que el mapa interno cree los datos que no existen, incluso si lo único que quería era hacer algún tipo de depuración, que por supuesto es un error de aplicación fatal.

El comportamiento Busco sería algo así como el operador de índice C#, que tiene un encuentro y una operación de conjunto y donde se puede lanzar una excepción si el comprador intenta acceder a algo que no existe:

class MyDictionary<TKey, TVal> 
{ 
    private Dictionary<TKey, TVal> dict = new Dictionary<TKey, TVal>(); 
    public TVal this[TKey idx] 
    { 
     get 
     { 
      if(!dict.ContainsKey(idx)) 
       throw KeyNotFoundException("..."); 

      return dict[idx]; 
     } 
     set 
     { 
      dict[idx] = value; 
     } 
    } 
} 

Por lo tanto, me pregunto por qué el gcc prefiere la llamada no const a través de la llamada const cuando no se requiere acceso no const.

Respuesta

0

Utilizará el método const en caso de crear una instancia de un objeto const de esa clase.

1

No puede obtener el efecto que desea. Cuando dict no es const, llamará a la versión no const del operador [].

C# es superior en este caso porque puede determinar si 'dict [n]' es parte de una tarea. C++ no puede hacer esto.

+0

Llamará a no-const a menos que la única versión disponible sea const y que no dé como resultado el error de "descarte de calificadores". Obviamente, el compilador sabe qué versiones existen, así que podría usar una que no destruya mis datos. Personalmente creo que esto es un mal comportamiento del compilador, así que infórmenme. ¿Por qué lo hicieron de esta manera cuando a la inversa (preferir no const) es una AFAICT más segura? – JonasW

+3

puede parecer más seguro, pero no es demasiado lógico, la * mejor * coincidencia es la no const porque es exactamente del mismo tipo y constness. Después de eso, puede buscar otras coincidencias. En general, esto resulta en la menor sorpresa. Si desea obtener seguridad, tiene que dirigirla para usarla –

0

Cualquier compilador de C++ debería funcionar de esta manera. No puede elegir una sobrecarga según si su función aparecerá en el lado izquierdo o derecho del operador de asignación. La sobrecarga se selecciona en función de si la instancia es const o no.

Las propiedades en C# y la sobrecarga basadas en la const-ness del método en C++ simplemente son diferentes cosas que tienen un propósito diferente.


Me pregunto si su pregunta está relacionada con Why can't we have an immutable version of operator[] for map?

Es una especie de posible emular distinguir entre el uso en el lado izquierdo de misiones y en otros contextos con una clase de proxy (espero que el término es correcto), pero en realidad no funciona demasiado bien y No lo recomendaría debido al operador de conversión implícito (tenga en cuenta que para generar el resultado se requiere un lanzamiento explícito).

#include <iostream> 
#include <map> 
#include <string> 
#include <stdexcept> 

using namespace std; 

template <typename KeyType, typename ValueType> 
class DictProxy; 

template <typename KeyType, typename ValueType> 
class ConstDictProxy; 

template <typename TKey, typename TValue> 
class Dictionary{ 
    public: 
    map<TKey, TValue> internal; 

    DictProxy<TKey, TValue> operator[](TKey const & key); 
    ConstDictProxy<TKey, TValue> operator[](TKey const & key) const; 
}; 

template <typename KeyType, typename ValueType> 
class DictProxy 
{ 
    std::map<KeyType, ValueType>* p_map; 
    const KeyType* key; 
    DictProxy(std::map<KeyType, ValueType>* p_map, const KeyType* key): p_map(p_map), key(key) {} 
    friend class Dictionary<KeyType, ValueType>; 
public: 
    void operator=(const ValueType& value) const { 
     cout << "operator[] used on the left side of assignment with key " << *key << endl; 
     (*p_map)[*key] = value; 
    } 
    operator ValueType&() const { 
     cout << "operator[] used in a different context with " << *key << endl; 

     //you used at here 
     //it is in the C++0x standard, but generally not in online references? 
     typename std::map<KeyType, ValueType>::iterator it = p_map->find(*key); 
     if (it == p_map->end()) { 
      throw std::range_error("Missing key in map"); 
     } 
     return it->second; 
    } 
}; 

template <typename KeyType, typename ValueType> 
class ConstDictProxy 
{ 
    const std::map<KeyType, ValueType>* p_map; 
    const KeyType* key; 
    ConstDictProxy(const std::map<KeyType, ValueType>* p_map, const KeyType* key): p_map(p_map), key(key) {} 
    friend class Dictionary<KeyType, ValueType>; 
public: 
    operator const ValueType&() const { 
     cout << "operator[] const used in a different context with " << *key << endl; 
     typename std::map<KeyType, ValueType>::const_iterator it = p_map->find(*key); 
     if (it == p_map->end()) { 
      throw std::range_error("Missing key in map"); 
     } 
     return it->second; 
    } 
}; 

template <typename TKey, typename TValue> 
DictProxy<TKey, TValue> Dictionary<TKey, TValue>::operator[](TKey const & key) 
{ 
    return DictProxy<TKey, TValue>(&internal, &key); 
} 

template <typename TKey, typename TValue> 
ConstDictProxy<TKey, TValue> Dictionary<TKey, TValue>::operator[](TKey const & key) const 
{ 
    return ConstDictProxy<TKey, TValue>(&internal, &key); 
} 

int main(int argc, char* argv[]) 
{ 
    Dictionary<string, string> dict; 
    dict["1"] = "one"; 
    cout << "first one: " << string(dict["1"]) << endl; 

    const Dictionary<string, string>& r_dict = dict; 
    cout << "first one: " << string(r_dict["1"]) << endl; 
    return 0; 
} 

(Algunos reutilización de código debería ser posible repect SECO en la aplicación de DictProxy y ConstDictProxy.)


Sin embargo, si su pregunta tiene que ver con que, a continuación, la solución de la OMI es utilizar el at() método cuando no desea valores predeterminados agregados, y operator[] si lo hace. Sospecho que el primero es una adición con C++ 0x, sin embargo?

+0

Gracias por su tiempo. Voy a echar un vistazo a esto esta noche. – JonasW

0

Solo recuerda que en C++ eres el encargado y tomas las decisiones.

considerar en el operador no const podría cambiar el objeto si así se desea. Simplemente sucede que no lo haces. Es una elección completamente arbitraria por parte del codificador si se cambia el objeto o no. El compilador no tiene idea de cuál es tu intención. es decir, no hay una buena regla para que el compilador sepa usar const.

así que sí ... le dices al compilador qué tratar como const.

Cuestiones relacionadas