【问题标题】:Why do I have to define a hash function for each namespace as the unordered_set?为什么我必须为每个命名空间定义一个哈希函数作为 unordered_set?
【发布时间】:2021-04-22 22:05:42
【问题描述】:

我想为我正在编写的一个类创建一个散列函数,并且我想让散列函数成为该类的朋友,这样我就不必编写不必要的 getter 方法。为此,我遵循了this SO post 中接受的答案。但我希望能够将对象插入std::unordered_setboost::unordered_set。所以我是这样写的:

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

class Vec;
namespace std { // line [c]
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}

class Vec {
private:
    std::vector<int> v;
public:
    friend size_t std::hash<Vec>::operator ()(const Vec& v) const; // line [d]
    friend bool operator == (const Vec& lhs, const Vec& rhs) { return lhs.v == rhs.v; }
};

namespace std { // line [e]
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}

int main() {
    Vec v;
    std::unordered_set<Vec> s1; // line [f]
    s1.insert(v); // line [g]
    boost::unordered_set<Vec> s2; // line [a]
    s2.insert(v); // line [b]
}

但我发现在尝试编译它时出现了一长串错误。然后,当我删除行 [a,b] 时,它按预期编译并运行。然后,我没有删除行[a,b],而是(1)将它们留在里面,(2)删除行[f,g],(3)将行[c,d,e]更改为boost而不是std,再次代码会正确编译。最后,我尝试在 boost 命名空间中重复声明哈希结构:

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

class Vec;
namespace std {
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}
// new: {
namespace boost {
    template<>
    struct hash<Vec> {
    public:
        size_t operator()(const Vec &v) const;
    };
}
// }

class Vec {
private:
    std::vector<int> v;
public:
    friend size_t std::hash<Vec>::operator ()(const Vec& v) const;
    // new: {
    friend size_t boost::hash<Vec>::operator ()(const Vec& v) const;
    // }
    friend bool operator == (const Vec& lhs, const Vec& rhs) { return lhs.v == rhs.v; }
};

namespace std {
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}
// new: {
namespace boost {
    size_t hash<Vec>::operator()(const Vec &v) const {
        return boost::hash<std::vector<int> >()(v.v);
    }
}
// }

int main() {
    Vec v;
    std::unordered_set<Vec> s1;
    s1.insert(v);
    boost::unordered_set<Vec> s2;
    s2.insert(v);
}

我的问题是:为什么我必须在 stdboost 命名空间中创建一个散列函数才能让它工作?我会说我对原因有直觉,但我想要一个非常详细的解释。而且我想要任何替代解决方案来解决上述代码段中有很多重复代码的事实(但不是像 boost::unordered_set&lt;Vec, my_vec_class_hash&gt; 这样的东西,因为我希望它是“自动的”)。

【问题讨论】:

  • 就个人而言,我不会专门化 boost::std:: 而是编写名为 VecHasher 的类,然后使用 whatever::unordered_set&lt;Vec, VecHasher&gt;
  • 您甚至可以使用别名,例如 using unordered_vec_set = whatever::unordered_set&lt;Vec, VecHasher&gt;;
  • 为什么需要对无序集同时使用 boost 和 std 模板?听起来像是 XY 问题。
  • 我不了解 boost,但专业化 std::hash 按标准明确是 allowed。这是一种简洁明了的方式,没有任何问题。
  • @Ranoiaetep 不需要,看我的回答。

标签: c++ boost hash namespaces


【解决方案1】:

通过使用 Boost 的启用 ADL 的自定义点 hash_value,您可以大大减少混乱:

class Vec {
  private:
    std::vector<int> v;

    friend size_t hash_value(const Vec& v) {
        return boost::hash_range(begin(v.v), end(v.v));
    }
    friend bool operator==(const Vec& lhs, const Vec& rhs) {
        return lhs.v == rhs.v;
    }
};

实际上,在这种情况下,使用return boost::hash_value(v.v);,哈希函数可以更简单。

这已经足以让 Boost 的无序容器适用于您的类型:

boost::unordered_set<Vec> s2;
s2.insert(v);

添加标准支持

现在已经不是问题了:

template <> struct std::hash<Vec> : boost::hash<Vec> {};

现场演示

Live On Coliru

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

class Vec {
  private:
    std::vector<int> v;

    friend size_t hash_value(const Vec& v) {
        return boost::hash_value(v.v);
        //return boost::hash_range(begin(v.v), end(v.v));
    }
    friend bool operator==(const Vec& lhs, const Vec& rhs) {
        return lhs.v == rhs.v;
    }
};

template <> struct std::hash<Vec> : boost::hash<Vec> {};

int main() {
    Vec v;
    std::unordered_set<Vec> s1;
    s1.insert(v);
    boost::unordered_set<Vec> s2;
    s2.insert(v);
}

【讨论】:

  • 是的,效果很好!我花了一些时间弄清楚如何将类分成头文件和实现文件,然后我意识到我需要将 std::hash&lt;Vec&gt; 初始化放在头文件中。
  • 是的。这称为定义,特别是专业化的定义。也许你明白这一点,但一般来说,模板定义需要在所有可能实例化它们的地方都可见,所以need to be in the headers
猜你喜欢
  • 2016-02-14
  • 2014-12-19
  • 1970-01-01
  • 2019-02-19
  • 2015-03-23
  • 1970-01-01
  • 2021-03-02
  • 1970-01-01
  • 2015-12-04
相关资源
最近更新 更多