在C ++中重载[]以返回左值

时间:2011-06-08 04:28:23

标签: c++ operator-overloading

我正在编写一个简单的哈希映射类:

template <class K, class V> class HashMap;

实现非常正统:我有一个堆数组,当它变大时,它的大小会翻倍。该数组包含键/值对的小向量。

Vector<Pair<K, V> > *buckets;

我想以这样的方式重载下标运算符:

HashMap<int, int> m;
m[0] = 10; m[0] = 20;
m[2] = m[1] = m[0];

特别是

  • m[k] = v其中m不包含k,我想要添加一个新条目。
  • 对于m[k] = v m包含k的{​​{1}},我希望替换旧值。
  • 在这两种情况下,我都希望作业返回v

据推测,代码看起来像

V& operator[](K &key)
{
    if (contains(key))
    {
        // the easy case
        // return a reference to the associated value
    }
    else
    {
        Vector<Pair<K, V> > *buck = buckets + hash(k) % num_buckets;
        // unfinished
    }
}

我该如何处理未找到密钥的情况?如果可以的话,我宁愿避免将值复制到堆中。

我想我可以创建一个帮助类,它将赋值运算符和强制转换重载为V,但肯定有一个更简单的解决方案吗?

编辑:我没有意识到std :: map要求值类型具有零参数构造函数。我想我也会默认构造一个值。

4 个答案:

答案 0 :(得分:8)

  

如何处理未找到密钥的情况?

使用该键插入一个新元素,并返回对该新元素值的引用。实际上,您的伪代码变为等同于:

if (!contains(key))
    insert(Pair<K, V>(key, V()));
return reference_to_the_element_with_that_key;

这正是您的要求。您说“对于m[k] = v其中m不包含k,我想要添加一个新条目。”

答案 1 :(得分:6)

  

我该如何处理未找到密钥的情况?

std::map创建一个新对象,并将其插入到地图中,并返回其引用。你也可以这样做。

或者,您可以像.NET地图抛出的方式一样抛出异常KeyNotFoundException。当然,您必须自己定义KeyNotFoundException,可能来自std::runtime_exception

顺便说一下,作为一般规则,始终将operator[]配对为:

V &operator[](const K &key);
const V &operator[](const K &key) const;

只是为了保持正确性。 但是,如果您决定创建一个新对象并将其插入到地图中,则在找不到该密钥时,此规则在此处不适用,因为const版本在这种情况下没有意义。

请参阅此常见问题:

答案 2 :(得分:4)

听起来你想要的是一个“智能参考”,你不能在C ++中一般地实现,因为you cannot overload the dot operator(以及其他原因)。

换句话说,不是返回对V的引用,而是向V返回一个“智能引用”,它包含一个指向V的指针。该智能引用将operator=(const V &v)实现为{{1}只需要一个复制构造函数(不是零参数构造函数)。

问题是智能引用必须在所有其他方面表现得像实际引用。我不相信你可以用C ++实现它。

一个不太完美的解决方案是让构造函数采用V的“默认”实例来初始化新条目。它可以默认为this->p = new V(v)

像这样:

V()

当V缺少零参数构造函数时,template<class K, class V> class HashMap { private: V default_val; public: HashMap(const V& def = V()) : default_val(def) { ... } ... }; 将无法编译;用户需要提供一个V对象,其值将在第一次访问密钥时返回。

这假设V有一个复制构造函数,当然。但是从你的例子来看,无论如何这似乎是一个要求。

答案 3 :(得分:2)

简单的解决方案就像std::map那样:构建一个新条目, 使用值类型的默认构造函数。这有两个 缺点:您将无法在[]和{1}}上使用HashMap const 无法使用没有默认值的值类型实例化HashMap 构造函数。第一个或多或少隐含在规范中, 这表示[]可能会修改地图。有几种解决方案 对于第二个:最简单的可能是传递一个实例 构造函数的“默认”值,它保存它,并使用它来复制 构造新实例,例如:

template <typename Key, typename Value>
class HashMap
{
    //  ...
    Value m_defaultValue;
public:
    HashMap( ..., Value const& defaultValue = Value() )
        : ... , m_defaultValue( defaultValue )...

    Value& operator[]( Key& key )
    {
        //  ...
        //  not found
        insert( key, m_defaultValue );
        //  return reference to newly inserted value.
    }
};

或者,您可以让operator[]返回代理,例如:

template <typename Key, typename Value>
class HashMap::Helper  //  Member class of HashMap
{
    HashMap* m_owner;
    Key m_key;
public:
    operator Value&() const
    {
        if ( ! m_owner->contains( m_key ) )
            m_owner->createEntryWithDefaultValue( m_key );
        return m_owner->getValue( m_key );
    }
    Helper& operator=( Value const& newValue ) const
    {
        m_owner->insert( m_key, newValue );
        return *this;
    }
};

请注意,在某人的情况下,您仍然需要默认值 写道:

v = m[x];

并且地图中不存在x。那些事情就像:

m[x].f();

不起作用。您只能复制整个值或分配给它。 (鉴于此,在这种情况下,我宁愿选择第一种解决方案 然而,在其他情况下,代理是唯一的解决方案,而我们 不得不忍受它。)