我有一个地图,使用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键值映射?
答案 0 :(得分:6)
原因是哈希映射(如std::unordered_map
)不仅依赖于哈希函数来确定两个密钥是否相等。哈希函数是第一个比较层,之后元素也总是按值进行比较。原因是即使具有良好的散列函数,您可能会发生冲突,其中两个不同的键产生相同的散列值 - 但您仍然需要能够在散列映射中保存这两个条目。有各种策略可以处理,您可以找到有关查找哈希映射的冲突解决方案的更多信息。
在您的示例中,两个条目具有相同的哈希值但值不同。这些值只是通过标准比较函数进行比较,该函数比较了不同的char*
指针。因此,值比较失败,您在地图中得到两个条目。要解决您的问题,您还需要为哈希映射定义自定义相等函数,可以通过为KeyEqual
指定第四个模板参数std::unordered_map
来完成。
答案 1 :(得分:1)
这会失败,因为unordered_map
没有并且不能单独依赖哈希函数来区分密钥,但它也必须将密钥与相同哈希进行比较以获得相等性。比较两个char指针比较指向的地址。
如果要更改比较,除了哈希之外,还要将KeyEqual
参数传递给地图。
struct MyKeyEqual
{
bool operator()(MyKey const &lhs, MyKey const &rhs) const
{
return std::strcmp(lhs, rhs) == 0;
}
};
答案 2 :(得分:1)
unordered_map
需要能够对密钥执行两个操作 - 检查相等性并获取哈希码。当然,允许两个不相等的密钥具有不同的哈希码。发生这种情况时,无序映射应用哈希冲突解决策略将这些不等密钥视为不同。
这正是为密钥提供字符指针时所发生的情况,并为其提供哈希实现:指针的默认相等比较启动,因此两个不同的指针产生两个不同的键,即使内容为相应的C字符串是相同的。
您可以通过提供KeyEqual
模板参数的自定义实现来修复它,以执行C字符串的实际比较,例如,通过调用strcmp
:
return !strcmp(lhsKey, rhsKey);
答案 3 :(得分:0)
您没有定义键的映射,而是定义键的指针映射。
typedef char const* const MyKey;
编译器可以优化"name"
的两个实例,并且只使用const数据段中的一个实例,但这可能发生与否。又名未定义的行为。
您的地图应包含密钥本身。将密钥设为std::string
或类似。