【问题标题】:What is the "right" way to use a GUID as the key in std::hash_map在 std::hash_map 中使用 GUID 作为键的“正确”方法是什么
【发布时间】:2014-06-09 04:52:43
【问题描述】:

我想说

std::hash_map<GUID, int> foo;

我相信要做到这一点,我必须创建一个

bool operator < (const GUID &guid1, const GUID &guid2);
std::size_t hash_value(GUID const &b);

比较 GUID 的正确方法是什么? (memcmp?) - 生成哈希的正确方法是什么?

如果有人能充实这两个功能,那就太好了,我已经阅读了几十篇文章,这些帖子什么都做,但给出了最后的线索:-)

【问题讨论】:

  • 如果有用的话,如果你不喜欢直接调用memcmp,可以使用IsEqualGUID函数(反正上面的函数就是这样)。
  • 没有std::hash_map 这样的东西。反正不在标准 C++ 中。你在问什么?

标签: c++ visual-c++ boost stl


【解决方案1】:

documentation看来:

typedef struct _GUID {
  DWORD Data1;
  WORD  Data2;
  WORD  Data3;
  BYTE  Data4[8];
} GUID;

可能有几种可能

构建自己的

为了比较,我会逐项进行比较

bool operator < (const GUID &guid1, const GUID &guid2) {
    if(guid1.Data1!=guid2.Data1) {
        return guid1.Data1 < guid2.Data1;
    }
    if(guid1.Data2!=guid2.Data2) {
        return guid1.Data2 < guid2.Data2;
    }
    if(guid1.Data3!=guid2.Data3) {
        return guid1.Data3 < guid2.Data3;
    }
    for(int i=0;i<8;i++) {
        if(guid1.Data4[i]!=guid2.Data4[i]) {
            return guid1.Data4[i] < guid2.Data4[i];
        }
    }
    return false;
}

对于散列...我会选择 UuidHash 函数(请注意,GUID 是 UUID 的一种形式,如 UUID definition 所示)

回到字符串

使用StringFromCLSID 从 GUID 中获取字符串...一旦有了字符串,您就拥有了所有的运算符。

...这可能更贵。

【讨论】:

  • 感谢@jsantander - 你觉得使用 memcmp 有什么问题吗?返回 memcmp(&guid1, &guid2, sizeof(GUID))
  • @stuck 可能会起作用...但是如果您偏执,结构布局可能会留下对齐空白(不确定是否是这种情况),这些空白将具有您将在比较。
  • 哈希映射实际上不需要小于比较,只有树(常规,有序)映射。
【解决方案2】:

C++ 库中没有 std::hash_map 并且 stdext::hash_map 是一个过时的类(参见 http://msdn.microsoft.com/en-us/en-en/library/0d462wfh.aspx)。

在 std::unordered_map 中使用 GUID:

#include <climits>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <unordered_map>

// Adopted from http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931%28v=vs.85%29.aspx
typedef struct _GUID
{
    std::uint32_t Data1;
    std::uint16_t Data2;
    std::uint16_t Data3;
    std::uint8_t  Data4[8];
} GUID;

// Ensure it has 128 bits
static_assert(sizeof(_GUID) == 128/CHAR_BIT, "GUID");

// The compare operator is required by std::unordered_map
inline bool operator == (const GUID& a, const GUID& b) {
    return std::memcmp(&a, &b, sizeof(GUID)) == 0;
}

// A wrapper to create a GUID
inline GUID make_guid() {
    // Should return the value of some library function. 
    return GUID();
}

// Specialize std::hash
namespace std {
    template<> struct hash<GUID>
    {
        size_t operator()(const GUID& guid) const noexcept {
            const std::uint64_t* p = reinterpret_cast<const std::uint64_t*>(&guid);
            std::hash<std::uint64_t> hash;
            return hash(p[0]) ^ hash(p[1]);
        }
    };
}

// Usage
int main(void) {
    typedef std::unordered_map<GUID, int> map_type;
    map_type m;
    m.insert(map_type::value_type(make_guid(), 0));
    m.insert(map_type::value_type(make_guid(), 1));
    m.insert(map_type::value_type(make_guid(), 2));
}

【讨论】:

  • 您应该真正静态断言 GUID 的对齐方式 >= std::uint64_t。例如#include &lt;type_traits&gt; static_assert(std::alignment_of&lt;GUID&gt;::value &gt;= std::alignment_of&lt;std::uint64_t&gt;::value, "")。对我来说,这个断言失败了。 GUID 与 std::uint32_t 具有相同的对齐方式
【解决方案3】:

由于 GUID 只是 POD,应该可以这样做:

namespace std
{
    template<> struct hash<GUID> : public std::_Bitwise_hash<GUID>
    {
    };
}

【讨论】:

  • TIL POD 表示“普通旧数据”
  • test.cpp:6:61: error: expected template-name before '&lt;' token template&lt;&gt; struct hash&lt;GUID&gt; : public std::_Bitwise_hash&lt;GUID&gt; {};
【解决方案4】:

对于 Windows 实现,我会直接选择 ::UuidHash()。所以哈希函数会变成这样:

#include <rpc.h>

namespace std
{
    template<> struct hash<GUID>
    {
        size_t operator()(const GUID &Value) const
        {
            RPC_STATUS status = RPC_S_OK;
            return ::UuidHash(&const_cast<GUID&>(Value), &status);
        }
    };
}

备注:

  • 有趣的是 ::UuidHash() 只返回一个无符号的短整数,但这似乎足以散列 GUID(为什么 MS 应该这样做?)。
  • 对于 unordered_map/unordered_set 而言,不需要 less()、operator
  • 如果出于其他目的需要进行比较,请使用 ::UuidCompare() 并将其隐藏在重载运算符中以获得语法糖(仍在等待太空飞船运算符实现...)。
  • 对于适用于基于散列的 STL 容器的散列函数,它不需要尽可能少的冲突。更重要的是 fast 散列函数。如果您的冲突可以接受,它只会导致存储多个值的存储桶,但事情仍然可以操作。所以 ::UuidHash() 返回的(可能是次优的?)无符号短应该没有问题。

【讨论】:

    【解决方案5】:

    您的 GUID 是什么类型的?考虑下面这段代码:

    struct MyCompare : binary_function<SomeClass, SomeClass, bool>
    {
        //constructor
        MyCompare(int (SomeClass::*p)() const, int (SomeClass::*p2)() const) : pointer(p), pointer2(p2) {}
    
        //comparison functor
    
        bool operator < (SomeClass const& left, SomeClass const& right)
        {
            if ((left.*pointer)() != (right.*pointer)())
            {
                return less<int>()((left.*pointer)(), (right.*pointer)());
            }
            else
            {
                return less<int>()((left.*pointer2)(), (right.*pointer2)());
            }
        }
        private:
        int (SomeClass::*pointer)() const;
        int (SomeClass::*pointer2)() const;
    };
    

    这是针对 2 个 int 类型字段的复合键。排序第一个指针,然后稳定排序第二个指针。代码可能不会第一次运行,但你明白了。

    【讨论】:

      【解决方案6】:

      首先,鉴于 GUID 是一个通用概念,您应该指定您正在谈论的 GUID 类。我猜它是thisMicrosoft Windows,因为你标记了visual-c++....

      “哈希 GUID”的快速网络搜索显示 Microsoft 提供了推荐的哈希,请参阅 here。听起来它有重大缺陷,描述为here

      如果由于某种原因您不能或不想使用它,请考虑数据成员:

      typedef struct _GUID {
        DWORD Data1;
        WORD  Data2;
        WORD  Data3;
        BYTE  Data4[8];
      } GUID;
      

      这些非 C++ 标准类型记录在 here。 您可以通过以下方式获得合理的 32 位哈希:

      DWORD hash(const GUID& g)
      {
          return std::hash(g.Data1 ^
                           ((g.Data2 << 16) | g.Data3) ^
                           *(DWORD*)(g.Data4) ^
                           *(DWORD*)(g.Data4 + 4);
      }
      

      要理解这一点 - 它有效地采用了这样的字段:

                    [16 bits here]         [16 bits here]
            g.Data1=0101100101010010:1001010101001010
            g.Data2=1011110101001001 1011101010101010=g.Data3
      g.Data4[0..3]=1010111010101010:0011101001000101
      g.Data3[4..7]=1101111010100100:1010100011010110
               hash=1001010000010101:1011110101110001
      

      从上到下,它将所有位异或在一起,因此如果上面有奇数个 1 位,则上面的最终哈希线为 1。如果你想要一个更强的散列,你可以将单个贡献值左右移位,例如3-16 位,对两者进行异或运算,或使用 hash_combine() 函数(google it!)。

      哈希映射不需要operator&lt;(排序二叉树只需要一个-即std::map),但确实需要能够比较等价...即operator==。您确实可以使用 memcpy 进行比较 - 考虑到字段大小和顺序,很明显结构不需要填充,因此所有位对于比较都是有意义的。你必须使用memcmp,分别比较每个字符,或者使用更多的类型转换,所以我只用一个memcmp....

      【讨论】:

      • MSFT 提供的哈希是托管的,如果我不提供操作符
      • @stuck:当然……如果您不编写托管代码,我提供了一个替代方案 - “出于某种原因,您不能……使用它”。回复operator&lt; - 哈希映射不应该需要它 - 它可能是从其他代码调用的。
      猜你喜欢
      • 2013-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-09
      • 1970-01-01
      相关资源
      最近更新 更多