C ++: Почему при доступе к operator [] gcc предпочитает не const, а const? - PullRequest
7 голосов
/ 17 марта 2010

Этот вопрос может быть более уместно задан в отношении C ++ в целом, но, поскольку я использую gcc в linux, это контекст. Рассмотрим следующую программу:

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

При выполнении программы вывод:

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

Я хотел бы, чтобы компилятор выбрал метод operator[]const во втором вызове. Причина в том, что без использования dict ["1"] раньше, вызов operator[] заставляет внутреннюю карту создавать данные, которые не существуют, даже если единственное, что я хотел, это сделать какой-нибудь отладочный вывод, какой из Конечно, фатальная ошибка приложения.

Поведение, которое я ищу, будет что-то вроде оператора индекса C #, который имеет операцию get и set, и где вы можете вызвать исключение, если получатель пытается получить доступ к чему-то, что не существует:

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

Таким образом, мне интересно, почему gcc предпочитает неконстантный вызов, а не константный, когда неконстантный доступ не требуется.

Ответы [ 4 ]

1 голос
/ 17 марта 2010

Вы не можете получить желаемый эффект. Когда dict неконстантный, он вызывает неконстантную версию оператора [].

C # лучше в этом случае, потому что он может определить, является ли 'dict [n]' частью присвоения. C ++ не может этого сделать.

0 голосов
/ 17 марта 2010

Просто помните, что в C ++ вы ответственны и можете принимать решения.

учтите, что в неконстантном операторе вы можете изменить объект, если пожелаете.Просто так бывает, что нет.Это совершенно произвольный выбор кодера, менять объект или нет.Компилятор понятия не имеет, каково ваше намерение.т. е. нет хорошего правила для компилятора знать, как использовать const.

, так что да .... вы говорите компилятору, что считать const.

0 голосов
/ 17 марта 2010

Любой компилятор C ++ должен работать таким образом. Вы не можете выбрать перегрузку в зависимости от того, будет ли ваша функция отображаться слева или справа от оператора присваивания. Перегрузка выбирается в зависимости от того, является ли экземпляр постоянным или нет.

Свойства в C # и перегрузка на основе константности метода в C ++ просто оказываются разными вещами, которые служат разным целям.


Интересно, связан ли ваш вопрос с Почему у нас не может быть неизменной версии оператора [] для карты ?

Можно эмулировать различие между использованием в левой части присваивания и в других контекстах с прокси-классом (я надеюсь, что этот термин правильный), но это не очень хорошо работает, и я не рекомендую его из-за оператора неявного преобразования (обратите внимание, что для вывода результата требуется явное приведение).

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

(Некоторое повторное использование кода должно позволять игнорировать DRY при реализации DictProxy и ConstDictProxy.)


Однако, если ваш вопрос связан с этим, то решение IMO состоит в том, чтобы использовать метод at(), когда вы не хотите добавлять значения по умолчанию, и operator[], если вы это делаете. Я подозреваю, что первый является дополнением к C ++ 0x, хотя?

0 голосов
/ 17 марта 2010

Он будет использовать метод const в случае, если вы создаете экземпляр объекта const этого класса.

...