2010-03-17 8 views
7

Diese Frage könnte in Bezug auf C++ im Allgemeinen besser gestellt werden, aber da ich gcc unter Linux verwende, ist das der Kontext. folgendes Programm vor:C++: Warum bevorzugt gcc beim Zugriff auf operator [] nicht-const über const?

#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; 
} 

Wenn das Programm ausgeführt wird, ist die Ausgabe:

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

Was Ich mag würde, ist der Compiler wählt die operator[]const Methode stattdessen in dem zweiten Aufruf zu haben. Der Grund dafür ist, dass der Aufruf von operator[] ohne die Verwendung von dict ["1"] dazu führt, dass die interne Karte die Daten erzeugt, die nicht existieren, selbst wenn das einzige, was ich wollte, eine Debugausgabe ist, was natürlich ist ein schwerwiegender Anwendungsfehler

Das Verhalten ich suche so etwas wie der C# Index-Operator sein würde, die ein Get- und Set-Betrieb hat und wo Sie eine Ausnahme auslösen könnten, wenn der Getter etwas zuzugreifen versucht, der nicht existiert:

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

So frage ich mich, warum der gcc den nichtkonstanten Aufruf über den Const-Aufruf bevorzugt, wenn nicht-const-Zugriff nicht erforderlich ist.

Antwort

0

Es wird die Methode const für den Fall verwenden, dass Sie ein const-Objekt dieser Klasse instanziieren.

1

Sie können nicht den gewünschten Effekt erzielen. Wenn dict nicht-const ist, wird es die nicht-konstante Version von operator [] aufrufen.

C# ist in diesem Fall besser, weil es feststellen kann, ob 'dict [n]' Teil einer Zuweisung ist. C++ kann das nicht tun.

+0

Es wird nicht-const aufgerufen, es sei denn, die einzige verfügbare Version ist const und führt nicht zum Fehler "discard qualifiers". Offensichtlich weiß der Compiler, welche Versionen existieren, also könnte er auch die verwenden, die meine Daten nicht zerstört. Ich persönlich denke das ist ein schlechtes Compilerverhalten, also erleuchte mich. Warum haben sie es so gemacht, wenn umgekehrt (lieber nicht-const) sicherer AFAICT ist? – JonasW

+3

scheint sicherer, aber es ist nicht zu logisch, die * beste * Übereinstimmung ist die nicht const, wie es von genau der gleichen Art und Konstanz ist. Danach kann es nach anderen Übereinstimmungen suchen. Im Allgemeinen führt dies zu der geringsten Überraschung. Wenn Sie const Sicherheit wollen, müssen Sie es anweisen, es zu verwenden –

0

Jeder C++ - Compiler sollte auf diese Weise funktionieren. Sie können eine Überladung nicht basierend darauf auswählen, ob Ihre Funktion auf der linken oder rechten Seite des Zuweisungsoperators angezeigt wird. Die Überladung wird basierend darauf ausgewählt, ob die Instanz const ist oder nicht.

Eigenschaften in C# und Überladen basierend auf Methode Const-Ness in C++ sind einfach verschiedene Dinge, die einen anderen Zweck erfüllen.


Ich frage mich, ob Ihre Frage zu Why can't we have an immutable version of operator[] for map verwandt ist?

Es ist möglich zu emulieren, zwischen der Verwendung auf der linken Seite der Zuweisung und in anderen Kontexten mit einer Proxy-Klasse zu unterscheiden (ich hoffe, der Begriff ist richtig), aber es funktioniert nicht wirklich gut und Ich würde es wegen des impliziten Konvertierungsoperators nicht empfehlen (beachten Sie, dass zur Ausgabe des Ergebnisses eine explizite Umwandlung erforderlich ist).

#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; 
} 

(Einige Code-Wiederverwendung sollte möglich sein, DRY repect wenn DictProxy und ConstDictProxy implementieren.)


Allerdings, wenn Ihre Frage, dass verwandt ist, dann IMO die Lösung die at() zu verwenden ist Methode, wenn Sie keine Standardwerte hinzufügen möchten, und operator[], wenn Sie dies tun. Ich vermute, dass ersteres eine Ergänzung mit C++ 0x ist, obwohl?

+0

Vielen Dank für Ihre Zeit. Ich werde mir das heute Abend ansehen. – JonasW

0

Denken Sie daran, in C++ sind Sie verantwortlich und müssen die Entscheidungen treffen.

Betrachten Sie im non-const-Operator könnten Sie das Objekt ändern, wenn dies gewünscht wird. Es passiert einfach, dass du es nicht tust. Es ist völlig willkürlich, ob der Coder das Objekt ändert oder nicht. Der Compiler hat keine Ahnung, was Ihre Absicht ist. Das heißt, es gibt keine gute Regel, damit der Compiler weiß, const zu verwenden.

ja ja .... Sie sagen dem Compiler was als const zu behandeln ist.

Verwandte Themen