【问题标题】:Hash specialization for table of function pointer函数指针表的哈希特化
【发布时间】:2014-05-14 09:15:01
【问题描述】:

更新以粗体

我正在为函数指针表编写一个哈希函数函数指针和函数表的结构不能修改(即它们已发布给第三方)。基于Can std::hash be used to hash function pointers?,std::hash 可用于函数指针。采用它,它产生以下解决方案。

这个解决方案的繁琐之处在于,每次我们向 FuncPointers 结构添加新的 API 时,我们都必须修改哈希特化以添加相应的更改(即 hashFunc(hashedValue, pFuncs->func3) )。

我想知道是否有更好的方法来实现函数指针的这种散列,从而可以避免对散列特化的持续修改?

typedef void (*func_type1) (int);
typedef void (*func_type2) (double);

typedef struct FuncPointers
{
    func_type1 func1;
    func_type2 func2;
    ...
} FuncPointers;

template <typename T> void hashFunc (size_t & HashedValue, T funcPointer)
{
    std::hash<T> hash;
    HashedValue ^= hash(funcPointer); // the XOR operator is randomly picked
}

namespace std
{
    template<> struct hash<FuncPointers>
    {
        size_t operator()(FuncPointers *pFuncs)
        {
            size_t hashedValue = 0;
            hashFunc(hashedValue, pFuncs->func1);
            hashFunc(hashedValue, pFuncs->func2);
            ...

            return hashedValue;
        }
    };
}

【问题讨论】:

标签: c++ templates hash


【解决方案1】:

从这个开始:https://stackoverflow.com/a/7115547/1774667

它为std::tuple 提供了一个hash_tuple::hash&lt;Tuple&gt;,它是一个有效的高质量哈希器(具有组合和递归支持!)。

接下来,将FuncPointers改成如下:

struct FuncPointers:std::tuple<func_type1, func_type2 /*, ...*/> {

  // optional:
  func_type1 func1() const { return std::get<0>(*this); }
  func_type1& func1() { return std::get<0>(*this); }
  //...
};
namespace std {
  template<>
  struct hash<FuncPointers> {
    template<typename... Ts>
    std::size_t operator()( std::tuple<Ts...> const& funcs ) const {
      return hash_tuple::hash<std::tuple<Ts...>>{}(funcs);
    }
  };
}

它将您的std::hash&lt;FuncPointers&gt; 重定向到在FuncPointers 的父级上调用hash_tuple::hash&lt;std::tuple&lt;...&gt;&gt;。如果您不想从 std::tuple 继承,将其更改为 has-a 而不是 is-a 关系应该很容易。

可选的func() 访问器让您更接近旧界面(只需要添加()),但也添加了样板。

另一种选择是:

template<unsigned N>
auto func() const->decltype( std::get<N>(*this) ){ return std::get<N>(*this); }
template<unsigned N>
auto& func()->decltype( std::get<N>(*this) ){ return std::get<N>(*this); }

funcPointers.func1 更改为funcPointers.func&lt;1&gt;(),但在您添加新的func 时会删除大量样板,并且与funcPointers 的旧界面非常相似。

如果使用旧接口的代码不多,使用std::get&lt;N&gt;() 是有意义的。

如果您的名称比func1 更具描述性,并且您仅将其用于示例,则可以将函数名称的枚举与上面的std::getfunc&lt;X&gt; 一起使用。如果您使用func&lt;X&gt;,您甚至可以使其类型安全(强制使用命名函数)。

【讨论】:

  • 谢谢雅克。我没有提到的一个限制(我的不好)是我无法更改函数指针的结构以及函数表(即这些结构已发布给第三方,因此我们无法修改它们)。我会仔细看看你的建议,看看我是否可以用这个限制来合理化它。
  • @lancery 你可以编写一个get_tie 方法,将每个变量包装成一个std::tie (这与手动维护hash 的代码大致相同),或者你可以等待反射到达,可能在 C++17 之前。 static_assert 表示(您处理的函数数量)* sizeof( 函数指针 ) == sizeof(FuncPointers) 可能有用。
  • 因为这是一个函数指针表,我们可以做一些简化的假设,假设它们都是相同的大小(即通过使用 size_t)并以某种方式遍历它们吗?
  • @lancery auto begin = reinterpret_cast&lt;void const*const*&gt;(&amp;fp); auto end = begin+sizeof(fp)/sizeof(void const*); 然后在beginend 的范围内进行累积散列?危险包括:成员之间的填充,前面的额外垃圾(去 POD),额外的垃圾或末尾的填充。我们可以通过一些匿名的structunion 操作让这变得不那么古怪——愿意限制您使用的编译器吗?
  • 这是使用 VC++ 编译器编译的。我们需要担心填充吗?我的理解是,鉴于它是一个直接的结构定义(不是成员结构,所以不必担心胖指针),并且函数指针是 4 字节或 8 字节,因此它们自然对齐。
【解决方案2】:

最好将FuncPointers 设为std::tuple&lt;func_type1, func_type2&gt;。然后在散列上查看this answer

顺便说一句,typedef struct FuncPointers { } FuncPointers 是一种 C 主义,在 C++ 中从来没有必要。

【讨论】:

  • hash 特化为 std::tuple&lt;Ts...&gt; 是未定义的行为。链接的答案使用对称的^,这在散列时是个坏主意。
  • @Yakk:我没有说你应该专门化std::hash,只需提供你自己的哈希值作为std::unordered_set 的第二个参数。你说得对,简单的^ 是个坏主意,但链接答案的相关想法是枚举元组成员。
  • OP 专门针对 std::hash —— 用 std::tuple 替换 FuncPointers 使得这不可能(好吧,可能,但不再合法!)。您链接到的答案专门std::hash&lt;std::tuple&lt;Ts...&gt;&gt;,这是未定义的行为(不需要诊断),更糟糕的是通常有效。我只是说你的回答会一次以 2-3 种不同的方式引导 OP(或其他阅读它的人)走上错误的道路。
猜你喜欢
  • 2020-07-23
  • 2014-05-22
  • 1970-01-01
  • 2021-06-18
  • 2012-09-19
  • 1970-01-01
  • 2018-06-10
  • 1970-01-01
  • 2016-03-25
相关资源
最近更新 更多