C++: Почему gcc предпочитает неконстанту по константе при доступе к оператору []?

Этот вопрос можно было бы более соответственно задать относительно 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#, который имеет получение и операцию присвоения и где Вы могли выдать исключение, если метод считывания пытается получить доступ к чему-то, что не существует:

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 предпочитает, чтобы неконстанта вызвала вызов константы, когда доступ неконстанты не требуется.

7
задан JonasW 17 March 2010 в 20:17
поделиться

4 ответа

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

0
ответ дан 7 December 2019 в 20:35
поделиться

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

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

1
ответ дан 7 December 2019 в 20:35
поделиться

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

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

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

0
ответ дан 7 December 2019 в 20:35
поделиться

Любой компилятор 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 () метод, если вы не хотите добавлять значения по умолчанию, и оператор [] , если хотите. Я подозреваю, что первый вариант является дополнением к C ++ 0x?

0
ответ дан 7 December 2019 в 20:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: