首页 > 解决方案 > 为自定义对象指针重载 boost::hash_value

问题描述

介绍

大家好,我尝试使用boost::unordered_set自定义类类型。该类存储有关坐标和其他几个值的信息,但只有坐标用于创建散列值。现在,如果我想插入一个点并且已经有一个坐标相等的点(因此是一个集合),我需要从原始对象更改第三个值(就像object.isDuplicate = true 非常简化的那样)。请不要过于拘泥于 bool 值和重复检测,因为它有点复杂,但它应该只表明我需要对存储的类进行非常量访问。我只能使用 boost 1.53 和 C++03 和 GCC 4.4.3

问题

现在的问题是,当我尝试插入一个点时,boost::unordered_set::insert我得到一个点pair<iterator, bool>,其中第一个成员是插入或原始条目的不可变迭代器,第二个是bool指示值是否已插入。不幸的是,我不能用不可变的迭代器改变值,所以我不得不想一些不同的东西。所以我现在尝试在集合中存储一个指向我的对象的指针,然后通过这个指针访问它以更改值(这应该没问题,因为该值与散列值无关,因此不会改变键)。所以我试图重载boost::hash_value函数以接受指向我的类的指针,如下所示:

size_t hash_value(const A * a) {
    size_t seed = 0;
    boost::hash_combine(seed, a->a);
    boost::hash_combine(seed, a->b);
    return seed;
}

但是unordered_set即使我用unordered_set< A *, boost::hash<A *> >. 对于散列方面:当我尝试使用没有指针的集合时,它工作正常,但我无法更改值。

可能的问题

我在boost::hash 参考中搜索了一下,发现这个重载 template<typename T> std::size_t hash_value(T* const&);我认为它被用来代替我自己的重载(并且只是用对象地址散列)但是我想知道为什么我的编译器不提示重新定义这个函数(我在-Wall -Wextra -pedantic启用标志的情况下编译。

问题

那么这是真正的问题吗?如果是这样,我怎么能告诉我的编译器明确地使用我的自定义哈希函数?

代码

最后我写了一个小例子来测试一切

#include <iostream>
#include <string>
#include <boost/functional/hash.hpp>
#include <boost/unordered_set.hpp>

using boost::unordered_set;

struct A {
    double a;
    double b;
    bool isDup;
    A(const double a, const double b): a(a), b(b), isDup(false) {}
    A(const A & a): a(a.a), b(a.b), isDup(a.isDup) {}
    
    /* Two equal As ought to have a bitwise equal floating point value so this is okay */
    bool operator==(const A & a) const {
        if (a.a != this->a) return false;
        if (a.b != this->b) return false;
        return true;
    }
};


size_t hash_value(const A * a) {
    size_t seed = 0;
    boost::hash_combine(seed, a->a);
    boost::hash_combine(seed, a->b);
    std::cout << "Seed: " << seed << std::endl; /* This is not printed so i assume the function is not called */
    return seed;
}


int main() {
    A a1(1.2, 2.3);
    A a2(2.3, 3.4);
    A a3(3.4, 4.5);
    A a4(a1);
    
    unordered_set< A *, boost::hash<A *> > usa; /* This was unintended lol */
    if ( ! usa.insert(&a1).second ) std::cout << "Error " << a1.a << ", " << a1.b << " is already in set" << std::endl;
    if ( ! usa.insert(&a2).second ) std::cout << "Error " << a2.a << ", " << a2.b << " is already in set" << std::endl;
    if ( ! usa.insert(&a3).second ) std::cout << "Error " << a3.a << ", " << a3.b << " is already in set" << std::endl;
    if ( ! usa.insert(&a4).second ) {
        /* This is not called */
        std::cout << "Error " << a4.a << ", " << a4.b << " is already in set" << std::endl;
        (*(usa.insert(&a4).first))->isDup = true;
    }
}

标签: c++boosthashhashset

解决方案


您的原始功能存在几个问题hash_value

  1. 它必须在boost命名空间内,因为boost::hash<T*>调用boost::hash_value会禁用依赖于参数的名称查找。
  2. 在模板名称查找执行两次:在声明和实例化时间。在实例化时,仅执行与参数相关的名称查找,但它被 1 禁用。这就是为什么必须在定义之前声明散列函数的原因boost::hash(在 include 之前boost/hash.hpp)。

例如:

#include <cstddef> // std::size_t

struct A;
namespace boost { inline std::size_t hash_value(A* a); }

#include <iostream>
#include <string>
#include <boost/functional/hash.hpp>
#include <boost/unordered_set.hpp>

struct A { /*... */};

size_t boost::hash_value(A* a) {
    size_t seed = 0;
    boost::hash_combine(seed, a->a);
    boost::hash_combine(seed, a->b);
    std::cout << "Seed: " << seed << std::endl; /* This is not printed so i assume the function is not called */
    return seed;
}

此外,您需要指定自己的元素比较类,boost::unordered_set比较指针中的默认类。


附带说明一下,在组合多个成员的哈希方面,boost::hash和的设计std::hash并不理想。使用N3980 Types Don't Know #中的新哈希框架,我不能推荐足够多的东西。


推荐阅读