在命名空间 std 之外特化 std::hash 不安全吗?

Is specializing std::hash outside namespace std unsafe?

我知道我们只能向 namespace std 添加代码,这是在那里定义的模板的模板特化,否则行为是未定义的。

我已经从 C++ primer 实现了这段代码:

//namespace std
//{
    template <>
    class std::hash<Sales_data>
//   class hash<Sales_data>
    {
    public:
        using result_type = std::size_t;
        using argument_type = Sales_data;
        result_type operator()(argument_type const& ) const;
    };

    typename hash<Sales_data>::result_type
    hash<Sales_data>::operator()(argument_type const& arg) const
    {
        return hash<std::string>()(arg.isbn_) ^
                hash<unsigned int>()(arg.nCopySold_) ^
                hash<double>()(arg.revenue_);
    }
//}

Is specializing std::hash outside namespace std unsafe?

没有。 template<> class std::hash<Sales_data> { ... }; 在全局命名空间中的结果与

相同
namespace std {
template<>
class hash<Sales_data> { ... };
}

在全局命名空间中。

Shouldn't I do this or it is erroneous to do so?

这样做很好 - 但旧版本的 g++(我认为是版本 7 之前的版本,仍在使用中)会产生一条警告,如“warning: specialization of template in different namespace”,如果你这样做的话在问题中做。结合 -Werror 这可能会导致完全正确的代码无法编译。我建议使用 namespace { ... } 版本。


关于散列本身的注释:std::hash<any_type_smaller_or_of_equal_size_of_size_t> 可能是标准库中非常差的散列(更改 1 位不会翻转其他大约一半),返回相同的输出(位模式) 作为输入。这很常见,因为它非常快,并且在标准容器中用作 key 时可以满足所有需要。每个值都会得到一个唯一的哈希值,所以从这个角度来看它是完美的。将这些组合在一起时就会出现问题。碰撞将非常容易创建 - 并且您希望将碰撞保持在最低限度,以便在 unordered 容器中快速查找。您的 XOR 操作不会改变关于冲突很多的事实,因此我建议使用函数来组合哈希。 boost有一个常用的(boost::hash_combine)。

32 位 size_ts 看起来像这样(不确定在不同的 boost 版本中是否不同):

template <class T>
inline void hash_combine(std::size_t& seed, const T& v)
{
    std::hash<T> hasher;
    seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}

示例:

namespace stolen { // from boost - use the real boost version to get proper 64 bit hashes
    template <class T>
    inline void hash_combine(std::size_t& seed, const T& v)
    {
        std::hash<T> hasher;
        seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
}

namespace std {
    template <>
    class hash<Sales_data> {
    public:
        using result_type = std::size_t;
        using argument_type = Sales_data;

        result_type operator()(argument_type const& arg) const {
            result_type result = hash<std::string>{}(arg.isbn_);
            stolen::hash_combine(result, arg.nCopySold_);
            stolen::hash_combine(result, arg.revenue_);
            return result;
        }
    };
}

也有人提议将 hash_combine 的某些版本添加到标准库中:hash_combine() Again