在命名空间 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_);
}
//}
代码工作正常所以我是将代码注入命名空间 std: template <> class std::hash<Sales_data>
还是我只是在命名空间之外专门化 std::hash
?原始代码使用的是未注释的行。
我不应该这样做还是这样做是错误的?
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_t
s 看起来像这样(不确定在不同的 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
我知道我们只能向 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_);
}
//}
代码工作正常所以我是将代码注入命名空间 std:
template <> class std::hash<Sales_data>
还是我只是在命名空间之外专门化std::hash
?原始代码使用的是未注释的行。我不应该这样做还是这样做是错误的?
Is specializing
std::hash
outside namespacestd
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_t
s 看起来像这样(不确定在不同的 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