簡體   English   中英

相同的密鑰,std :: unordered_map的多個條目?

[英]Same key, multiple entries for std::unordered_map?

我有一個地圖插入多個值與C string類型相同的鍵。

我希望有一個具有指定鍵的條目。

然而,當唯一地識別密鑰時,地圖似乎將其地址考慮在內。

#include <cassert>
#include <iostream>
#include <string>
#include <unordered_map>

typedef char const* const MyKey;

/// @brief Hash function for StatementMap keys
///
/// Delegates to std::hash<std::string>.
struct MyMapHash {
public:
    size_t operator()(MyKey& key) const {
        return std::hash<std::string>{}(std::string(key));
    }
};

typedef std::unordered_map<MyKey, int, MyMapHash> MyMap;

int main()
{
    // Build std::strings to prevent optimizations on the addresses of
    // underlying C strings.
    std::string key1_s = "same";
    std::string key2_s = "same";
    MyKey key1 = key1_s.c_str();
    MyKey key2 = key2_s.c_str();

    // Make sure addresses are different.
    assert(key1 != key2);

    // Make sure hashes are identical.
    assert(MyMapHash{}(key1) == MyMapHash{}(key2));

    // Insert two values with the same key.
    MyMap map;
    map.insert({key1, 1});
    map.insert({key2, 2});

    // Make sure we find them in the map.
    auto it1 = map.find(key1);
    auto it2 = map.find(key2);
    assert(it1 != map.end());
    assert(it2 != map.end());

    // Get values.
    int value1 = it1->second;
    int value2 = it2->second;

    // The first one of any of these asserts fails. Why is there not only one
    // entry in the map?
    assert(value1 == value2);
    assert(map.size() == 1u);
}

調試器中的打印顯示該映射在插入之后包含兩個元素。

(gdb) p map
$4 = std::unordered_map with 2 elements = {
  [0x7fffffffda20 "same"] = 2,
  [0x7fffffffda00 "same"] = 1
}

如果委托給std::hash<std::string>的哈希函數只考慮它的值(這在代碼中斷言),為什么會發生這種情況呢?

此外,如果這是預期的行為,我如何使用帶有C字符串的映射作為鍵,但使用1:1鍵值映射?

原因是哈希映射(如std::unordered_map )不僅依賴於哈希函數來確定兩個鍵是否相等。 哈希函數是第一個比較層,之后元素也總是按值進行比較。 原因是即使具有良好的散列函數,您可能會發生沖突,其中兩個不同的鍵產生相同的散列值 - 但您仍然需要能夠在散列映射中保存這兩個條目。 有各種策略可以處理,您可以找到有關查找哈希映射的沖突解決方案的更多信息。

在您的示例中,兩個條目具有相同的哈希值但值不同。 這些值只是通過標准比較函數進行比較,該函數比較不同的char*指針。 因此,值比較失敗,您在地圖中得到兩個條目。 要解決您的問題,您還需要為哈希映射定義自定義相等函數,這可以通過為std::unordered_map指定第四個模板參數KeyEqual來完成。

這失敗是因為unordered_map沒有並且不能僅僅依賴於鍵的哈希函數來區分鍵,但它還必須將具有相同哈希的鍵與相等性進行比較。 比較兩個char指針比較指向的地址。

如果要更改比較,除了哈希之外,還要將KeyEqual參數傳遞給映射。

struct MyKeyEqual
{
    bool operator()(MyKey const &lhs, MyKey const &rhs) const
    {
        return std::strcmp(lhs, rhs) == 0;
    }
};

unordered_map需要能夠對密鑰執行兩個操作 - 檢查相等性並獲取哈希代碼。 當然,允許兩個不相等的密鑰具有不同的哈希碼。 發生這種情況時,無序映射應用哈希沖突解決策略將這些不等密鑰視為不同。

這正是當你為鍵提供一個字符指針時發生的事情,並為它提供一個哈希的實現:指針的默認相等比較開始,所以兩個不同的指針產生兩個不同的鍵,即使相應的C的內容字符串是一樣的。

您可以通過提供KeyEqual模板參數的自定義實現來修復它,以執行C字符串的實際比較,例如,通過調用strcmp

return !strcmp(lhsKey, rhsKey);

您沒有定義鍵的映射,而是定義鍵的指針映射。

typedef char const* const MyKey;

編譯器可以優化"name"的兩個實例,並且只在const數據段中使用一個實例,但這可能發生與否。 Aka未定義的行為。

您的地圖應包含密鑰本身。 將密鑰設為std::string或類似密鑰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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