簡體   English   中英

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

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM