我正在编写一个简单的哈希映射类:
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要求值类型具有零参数构造函数。我想我也会默认构造一个值。
答案 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();
不起作用。您只能复制整个值或分配给它。 (鉴于此,在这种情况下,我宁愿选择第一种解决方案 然而,在其他情况下,代理是唯一的解决方案,而我们 不得不忍受它。)