[英]Overloading [] in C++ to return lvalue
我正在寫一個簡單的 hash map class:
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
,我希望替換舊值。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
}
}
找不到密鑰的情況應該如何處理? 如果可以的話,我寧願避免將值復制到堆中。
我想我可以制作一個助手 class 重載賦值運算符和強制轉換為V
,但肯定有更簡單的解決方案嗎?
編輯:我沒有意識到 std::map 要求值類型具有零參數構造函數。 我想我也會默認構造一個值。
找不到密鑰的情況應該如何處理?
使用該鍵插入一個新元素並返回對該新元素值的引用。 實際上,您的偽代碼相當於:
if (!contains(key))
insert(Pair<K, V>(key, V()));
return reference_to_the_element_with_that_key;
這也正是您的要求。 您說“對於m[k] = v
其中m
不包含k
,我想添加一個新條目。”
找不到密鑰的情況應該如何處理?
std::map
創建一個新的 object,並將其插入到 map 中,並返回其引用。 你也可以這樣做。
或者,您可以拋出異常KeyNotFoundException
,就像 .NET map 拋出的方式一樣。 當然,您必須自己定義KeyNotFoundException
,可能源自std::runtime_exception
。
順便說一句,作為一般規則,始終將operator[]
成對實現為:
V &operator[](const K &key);
const V &operator[](const K &key) const;
只是為了 const 正確性。 但是,如果您決定創建一個新的 object 並將其插入 map,當找不到密鑰時,則此規則不適用於此處,因為const
版本在這種情況下沒有意義。
請參閱此常見問題解答:
聽起來您想要的是“智能參考”,您通常無法在 C++ 中實現它,因為您不能重載點運算符(以及其他原因)。
換句話說,不是返回對 V 的引用,而是返回對 V 的“智能引用”,其中包含指向 V 的指針。該智能引用將operator=(const V &v)
實現為this->p = new V(v)
,它只需要一個復制構造函數(不是零參數構造函數)。
問題是智能引用在所有其他方面都必須表現得像實際引用。 我不相信你可以在 C++ 中實現這個。
一個不完全的解決方案是讓您的構造函數采用 V 的“默認”實例來初始化新條目。 它可以默認為V()
。
像這樣:
template<class K, class V> class HashMap {
private:
V default_val;
public:
HashMap(const V& def = V()) : default_val(def) {
...
}
...
};
當 V 缺少零參數構造函數時, HashMap h
將無法編譯; 用戶需要提供一個 V object,當第一次訪問一個鍵時,它的值將被返回。
當然,這假設 V 有一個復制構造函數。 但從你的例子來看,這似乎是一個要求。
簡單的解決方案是像std::map
所做的那樣:使用值類型的默認構造函數構造一個新條目。 這有兩個缺點:您將無法在HashMap const
上使用[]
,並且您無法使用沒有默認構造函數的值類型實例化HashMap
。 第一個在規范中或多或少是隱含的,它說[]
可能會修改 map。 第二種有幾種解決方案:最簡單的可能是將“默認”值的實例傳遞給構造函數,構造函數保存它,並使用它來復制構造新實例,例如:
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
不存在於 map 中。 像這樣的事情:
m[x].f();
不會工作。 您只能將整個值復制出來或分配給它。 (鑒於此,在這種情況下,我更喜歡第一個解決方案。但是,在其他情況下,代理是唯一的解決方案,我們必須接受它。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.