C ++:为什么gcc在访问operator []时更喜欢非const over const?

时间:2010-03-17 20:17:13

标签: c++ gcc const

关于C ++,这个问题可能更适合问题,但是因为我在linux上使用gcc就是上下文。 请考虑以下程序:

#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操作,如果getter尝试访问不存在的内容,则可以抛出异常:

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

因此,我想知道为什么当不需要非const访问时,gcc更喜欢对const调用的非const调用。

4 个答案:

答案 0 :(得分:1)

你无法获得想要的效果。当dict是非const时,它将调用operator []的非const版本。

在这种情况下,C#是优越的,因为它可以确定'dict [n]'是否是作业的一部分。 C ++不能这样做。

答案 1 :(得分:0)

在实例化该类的const对象的情况下,它将使用const方法。

答案 2 :(得分:0)

任何C ++编译器都应该以这种方式工作。您无法根据您的函数是出现在赋值运算符的左侧还是右侧来选择过载。根据实例是否为const来选择重载。

C#中的属性和基于C ++中方法常量的重载恰好是用于不同目的的不同东西。


我想知道您的问题是否与Why can't we have an immutable version of operator[] for map相关?

有可能模仿区分赋值左侧的使用和其他上下文与代理类的区别(我希望这个术语是正确的),但它并没有真正起作用我因为隐式转换运算符而不推荐它(请注意,要输出结果,需要显式转换)。

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

(在实现DictProxy和ConstDictProxy时,应该可以重用某些代码来重用DRY。)


但是,如果您的问题与此相关,那么解决方案IMO将在您不希望添加默认值时使用at()方法,如果您这样做,则使用operator[]。我怀疑前者是C ++ 0x的补充,但是?

答案 3 :(得分:0)

请记住,在C ++中,你负责并做出决定。

在非const运算符中考虑,如果需要,可以更改对象。碰巧你没有。编码人员是否完全任意选择是否更改对象。编译器不知道你的意图是什么。也就是说,没有好的规则让编译器知道使用const。

所以是的....你告诉编译器将什么视为const。