C++ std::char*值上的哈希值,而不是内存地址上的哈希值?

C++ std::char*值上的哈希值,而不是内存地址上的哈希值?,c++,c++11,hash,C++,C++11,Hash,如本文所述: C字符串没有专门化。哈希生成指针值(内存地址)的哈希,它不检查任何字符数组的内容 这意味着使用相同的char*值,可以生成不同的哈希代码。例如,拥有以下代码: //MOK and MOV are template arguments void emit(MOK key, MOV value) { auto h = hash<MOK>()(key); cout<<"key="<<key<<" h="<<h&l

如本文所述:

C字符串没有专门化。哈希生成指针值(内存地址)的哈希,它不检查任何字符数组的内容

这意味着使用相同的
char*
值,可以生成不同的哈希代码。例如,拥有以下代码:

//MOK and MOV are template arguments
void emit(MOK key, MOV value) {
    auto h = hash<MOK>()(key);
    cout<<"key="<<key<<" h="<<h<<endl;
    ...

如何为
char*
获取相同的哈希代码?我不想使用
boost

我以前不得不这样做,最后编写了一个函数来实现这一点,基本上与以下实现相同:

size\u t hash\u c\u字符串(const char*p,size\t s){
大小\u t结果=0;
常量大小\u t素数=31;
对于(尺寸i=0;i

请注意,这不是一个加密安全的散列,但它足够快,并产生良好的结果。

当然有一个简单(缓慢)的解决方案,即创建一个临时的
std::string
并对其进行散列。如果您不想这样做,恐怕您必须实现自己的哈希函数。可悲的是,当前C++标准库不提供从特定于对象的哈希解决方案中解开的通用哈希算法。(但这在未来可能会发生变化。)

假设你有一个函数

std::size_t
hash_bytes(const void * data, std::size_t size) noexcept;
这将获取一个地址和一个大小,并返回一个哈希值,该哈希值是根据地址后面的那个么多字节计算出来的。在这个函数的帮助下,您可以轻松地编写

template <typename T>
struct myhash
{
  std::size_t
  operator()(const T& obj) const noexcept
  {
    // Fallback implementation.
    auto hashfn = std::hash<T> {};
    return hashfn(obj);
  }
};
接下来,我将为32位和64位版本提供别名。参数取自

使用fnv1a\u 32=basic\u fnv1a;
使用fnv1a_64=基本fnv1a;
最后,我提供类型元函数来选择给定所需位数的算法版本

template <std::size_t Bits>
struct fnv1a;

template <>
struct fnv1a<32>
{
  using type = fnv1a_32;
};

template <>
struct fnv1a<64>
{
  using type = fnv1a_64;
};

template <std::size_t Bits>
using fnv1a_t = typename fnv1a<Bits>::type;
模板
结构fnv1a;
模板
结构fnv1a
{
使用类型=fnv1a_32;
};
模板
结构fnv1a
{
使用类型=fnv1a_64;
};
模板
使用fnv1a\u t=typename fnv1a::type;
有了这些,我们就可以出发了

constexpr std::size_t
hash_bytes(const void *const data, const std::size_t size) noexcept
{
  auto hashfn = fnv1a_t<CHAR_BIT * sizeof(std::size_t)> {};
  hashfn.update(data, size);
  return hashfn.digest();
}
constexpr std::size\u t
哈希字节(常量无效*常量数据,常量标准::大小大小)无异常
{
auto hashfn=fnv1a_t{};
更新(数据、大小);
返回hashfn.digest();
}

请注意此代码如何自动适应
std::size\t
为32或64位宽的平台。

在C++17中,您应该使用
std::hash
,它可以无缝工作,因为
const char*
可以隐式转换为它。

自从C++17添加了
std::hash
专门化后,您可以使用它它用于计算C字符串的哈希值

例如:

#include <string_view>
#include <cstring>

static size_t hash_cstr(const char *s)
{
    return std::hash<std::string_view>()(std::string_view(s, std::strlen(s)));
}

那么,您的目标是将
“hello,world”
std::string{“hello,world”}
哈希值设置为相同的值吗?对
std::string
有专门化,但对C字符串没有专门化。你能用
std::string
显示得到这个结果的实际代码吗?你不能用
std::string
s吗?你为什么不专门化
std::hash
?@vsoftco,因为它违反了中的全面禁止。为什么不直接专门化std::hash呢?它不应该是一个例外IIRC。而且在将来,我们可能能够做
std::hash(std::string\u view(“some\u key”))
,这应该很便宜。@BillLynch我也在想,这会很酷。但我还记得有人在争论
std::string_view
上的等价性应该用指针地址来定义还是指向内存。如果是前者,我们就不能用它进行散列。你知道当前状态是什么吗?@5gon12eder:cppreference说如果
string\u view(“abc”)==string\u view(“abc”)
那么
hash(string\u view(“abc”)==hash(string\u view(“abc”)
。所以,除非他们决定做一个无用的相等运算符,否则我们应该没事。这是一个糟糕的答案。谢谢。const char*s=“foo”h1=std::hash{}(std::string_view(s,std::strlen(s)))std::string s2(“foo”)h2=std::hash{}(s2)将s1==s2?@sandwood我看不出你在哪里定义s1。你是不是想问h1==h2?C++标准不要求从实例中代码<> H1和 H2将是相等的。然而,你也可以。
template <typename ResultT, ResultT OffsetBasis, ResultT Prime>
class basic_fnv1a final
{

  static_assert(std::is_unsigned<ResultT>::value, "need unsigned integer");

public:

  using result_type = ResultT;

private:

  result_type state_ {};

public:

  constexpr
  basic_fnv1a() noexcept : state_ {OffsetBasis}
  {
  }

  constexpr void
  update(const void *const data, const std::size_t size) noexcept
  {
    const auto cdata = static_cast<const unsigned char *>(data);
    auto acc = this->state_;
    for (auto i = std::size_t {}; i < size; ++i)
      {
        const auto next = std::size_t {cdata[i]};
        acc = (acc ^ next) * Prime;
      }
    this->state_ = acc;
  }

  constexpr result_type
  digest() const noexcept
  {
    return this->state_;
  }

};
using fnv1a_32 = basic_fnv1a<std::uint32_t,
                             UINT32_C(2166136261),
                             UINT32_C(16777619)>;

using fnv1a_64 = basic_fnv1a<std::uint64_t,
                             UINT64_C(14695981039346656037),
                             UINT64_C(1099511628211)>;
template <std::size_t Bits>
struct fnv1a;

template <>
struct fnv1a<32>
{
  using type = fnv1a_32;
};

template <>
struct fnv1a<64>
{
  using type = fnv1a_64;
};

template <std::size_t Bits>
using fnv1a_t = typename fnv1a<Bits>::type;
constexpr std::size_t
hash_bytes(const void *const data, const std::size_t size) noexcept
{
  auto hashfn = fnv1a_t<CHAR_BIT * sizeof(std::size_t)> {};
  hashfn.update(data, size);
  return hashfn.digest();
}
#include <string_view>
#include <cstring>

static size_t hash_cstr(const char *s)
{
    return std::hash<std::string_view>()(std::string_view(s, std::strlen(s)));
}
#include <functional>
// -> which finally includes /usr/include/c++/$x/bits/hash_bytes.h
#include <cstring>

static size_t hash_cstr_gnu(const char *s)
{
    const size_t seed = 0;
    return std::_Hash_bytes(s, std::strlen(s), seed);
}