所有哈希表都使用一个哈希函数,该函数将放入容器的元素的值映射到特定的存储桶。目标两个是相等的值始终生成相同的存储桶索引,而对于不同的值,理想情况下应处理不同的存储桶条目。对于任何传递值的范围,哈希函数应提供哈希值的良好分布。
哈希函数必须是一个函数或函数对象,它以元素类型的值作为参数并返回std::size_t类型的值。因此,不考虑当前的存储桶数。将返回值映射到有效存储桶索引的范围是在容器内部完成的。因此,目标是提供一个函数,用于映射在[0, size_t]范围内均匀分布的不同元素值。
下面是一个自定义哈希函数的示例:
#include
#include
class Customer
{
//...;
};
class CustomerHash
{
public:
size_t operator() (const Customer& c) const
{
return 1; //...;
}
};
int main()
{
std::unordered_set<Customer, CustomerHash> custset;
getchar();
return 0;
}
此处,CustomerHash是一个函数对象,用于定义Customer类的哈希函数。
除了将函数对象传递给容器的类型之外,还可以传递哈希函数作为构造参数。但请注意,必须相应地设置哈希函数的模板类型:
std::size_t customer_hash_func(const Customer& c)
{
return ...
}
std::unordered_set<Customer, std::size_t(*)(const Customer&)> customer_set(20, customer_hash_func);
此处,customer_hash_func()作为第二个构造参数传递。
如果未传递哈希函数,则使用默认哈希函数 std::hash<>
,该函数在
头文件中作为函数对象提供,用于常见类型:所有整数、所有浮点数、指针、字符串和一些特殊类型。对于其它类型,必须提供自己的哈希函数。
// 定义于头文件
template<class Key>
struct hash;
template<> struct hash<bool>;
template<> struct hash<char>;
template<> struct hash<signed char>;
template<> struct hash<unsigned char>;
template<> struct hash<char8_t>; // C++20
template<> struct hash<char16_t>;
template<> struct hash<char32_t>;
template<> struct hash<wchar_t>;
template<> struct hash<short>;
template<> struct hash<unsigned short>;
template<> struct hash<int>;
template<> struct hash<unsigned int>;
template<> struct hash<long>;
template<> struct hash<long long>;
template<> struct hash<unsigned long>;
template<> struct hash<unsigned long long>;
template<> struct hash<float>;
template<> struct hash<double>;
template<> struct hash<long double>;
template<> struct hash<std::nullptr_t>;
template< class T > struct hash<T*>;
std::hash<std::string> (C++11)
std::hash<std::u8string> (C++20)
std::hash<std::u16string> (C++11)
std::hash<std::u32string> (C++11)
std::hash<std::wstring> (C++11)
std::hash<std::pmr::string> (C++17)
std::hash<std::pmr::u8string> (C++20)
std::hash<std::pmr::u16string> (C++17)
std::hash<std::pmr::u32string> (C++17)
std::hash<std::pmr::wstring> (C++17)
std::hash<std::error_code> (C++11)
std::hash<std::bitset> (C++11)
std::hash<std::unique_ptr> (C++11)
std::hash<std::shared_ptr> (C++11)
std::hash<std::type_index> (C++11)
std::hash<std::vector<bool>> (C++11)
std::hash<std::thread::id> (C++11)
std::hash<std::optional> (C++17)
std::hash<std::variant> (C++17)
std::hash<std::string_view> (C++17)
std::hash<std::wstring_view> (C++17)
std::hash<std::u8string_view> (C++20)
std::hash<std::u16string_view> (C++17)
std::hash<std::u32string_view> (C++17)
std::hash<std::error_condition> (C++17)
std::hash<std::coroutine_handle> (C++20)
std::hash<std::basic_stacktrace> (C++23)
std::hash<std::stacktrace_entry> (C++23)
提供一个良好的哈希函数比听起来更棘手。根据经验,可以使用默认哈希函数来指定自己的哈希函数。一种简单的方法是简单地添加与哈希函数相关的那些属性的所有哈希值,比如:
class CustomerHash
{
public:
std::size_t operator() (const Customer& c) const
{
return std::hash<std::string>()(c.fname) +
std::hash<std::string>()(c.lname) +
std::hash<long>()(c.no);
}
};
此处,返回的哈希值只是Customer的fname、lname、no属性的哈希值的和。如果这些属性类型的预定义哈希函数对于给定值工作正常,则三个值的总和结果范围是[0, std::size_t)。根据常见的溢出规则,结果值也应合理地分布良好。
但是注意,这仍然是一个糟糕的哈希函数,并且提供良好的哈希函数可能非常棘手。此外,提供这样的哈希函数并不像它应该的那样容易。
更好的方法如下,使用Boost提供的哈希函数和更方便的接口:
#include
// from boost (functional/hash):
// see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html
template <typename T>
inline void hash_combine(std::size_t& seed, const T& val)
{
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
// auxiliary generic functions to create a hash value using a seed
template <typename T>
inline void hash_val(std::size_t& seed, const T& val)
{
hash_combine(seed,val);
}
template<typename T, typename... Types>
inline void hash_val (std::size_t& seed, const T& val, const Types&... args)
{
hash_combine(seed, val);
hash_val(seed, args...);
}
// auxiliary generic function to create a hash value out of a heterogeneous list of arguments
template <typename... Types>
inline std::size_t hash_val(const Types&... args)
{
std::size_t seed = 0;
hash_val(seed, args...);
return seed;
}
使用可变参数模板实现的便利函数允许使用任意数量的任何类型的元素调用hash_val()
,以处理所有这些值中的哈希值。例如:
class CustomerHash
{
public:
std::size_t operator()(const Customer& c) const
{
return hash_val(c.fname, c.lname, c.no);
}
}
在内部,hash_combine()
被调用,一些经验表明它是通用哈希函数的良好候选者。
特别是当哈希函数的输入值具有特定约束时,可能希望提供自己的特定哈希函数。在任何情况下,要验证自己的哈希函数的效果,可以使用bucket接口。
下面的示例演示如何为类型Customer定义和指定哈希函数和等价标准,它被用作一个无序集合的元素类型:
#include
#include
#include
template <typename T>
inline void hash_combine(std::size_t& seed, const T& val)
{
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
// auxiliary generic functions to create a hash value using a seed
template <typename T>
inline void hash_val(std::size_t& seed, const T& val)
{
hash_combine(seed, val);
}
template<typename T, typename... Types>
inline void hash_val (std::size_t& seed, const T& val, const Types&... args)
{
hash_combine(seed, val);
hash_val(seed, args...);
}
// auxiliary generic function to create a hash value out of a heterogeneous list of arguments
template <typename... Types>
inline std::size_t hash_val (const Types&... args)
{
std::size_t seed = 0;
hash_val(seed, args...);
return seed;
}
class Customer
{
private:
std::string fname;
std::string lname;
long no;
public:
Customer (const std::string& fn, const std::string& ln, long n)
: fname(fn), lname(ln), no(n) { }
friend std::ostream& operator << (std::ostream& strm, const Customer& c)
{
return strm << "[" << c.fname << "," << c.lname << "," << c.no << "]";
}
friend class CustomerHash;
friend class CustomerEqual;
};
class CustomerHash
{
public:
std::size_t operator()(const Customer& c) const
{
return hash_val(c.fname, c.lname, c.no);
}
};
class CustomerEqual
{
public:
bool operator()(const Customer& c1, const Customer& c2) const
{
return c1.no == c2.no;
}
};
template <typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr="")
{
std::cout << optstr;
typename T::const_iterator pos; // not static
for (pos = coll.begin(); pos != coll.end(); ++pos)
{
std::cout << *pos << ' ';
//for (const auto& elem : coll) { // c++11
//std::cout << elem << ’ ’;
}
std::cout << std::endl;
}
int main()
{
// unordered set with own hash function and equivalence criterion
std::unordered_set<Customer, CustomerHash, CustomerEqual> custset;
custset.insert(Customer("nico", "josuttis", 42));
PRINT_ELEMENTS(custset);
return 0;
}
/**
* output:
* [nico,josuttis,42]
*/
还可以使用 lambda 表达式来指定哈希函数。
#include
#include
#include
template<typename T>
inline void hash_combine(std::size_t& seed, const T& val)
{
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
// auxiliary generic functions to create a hash value using a seed
template<typename T>
inline void hash_val(std::size_t& seed, const T& val)
{
hash_combine(seed, val);
}
template<typename T, typename... Types>
inline void hash_val(std::size_t& seed, const T& val, const Types&... args)
{
hash_combine(seed, val);
hash_val(seed, args...);
}
// auxiliary generic function to create a hash value out of a heterogeneous list of arguments
template <typename... Types>
inline std::size_t hash_val(const Types&... args)
{
std::size_t seed = 0;
hash_val (seed, args...);
return seed;
}
class Customer
{
private:
std::string fname;
std::string lname;
long no;
public:
Customer(const std::string& fn, const std::string& ln, long n)
: fname(fn), lname(ln), no(n) { }
std::string firstname() const
{
return fname;
};
std::string lastname() const
{
return lname;
};
long number() const
{
return no;
};
friend std::ostream& operator<<(std::ostream& strm, const Customer& c)
{
return strm << "[" << c.fname << "," << c.lname << "," << c.no << "]";
}
};
template<typename T>
inline void PRINT_ELEMENTS(const T& coll, const std::string& optstr = "")
{
std::cout << optstr;
typename T::const_iterator pos; // not static
for (pos = coll.begin(); pos != coll.end(); ++pos)
{
std::cout << *pos << ' ';
}
std::cout << std::endl;
}
int main()
{
// lambda for user-defined hash function
auto hash = [](const Customer& c) {
return hash_val(c.firstname(), c.lastname(), c.number());
};
// lambda for user-defined equality criterion
auto eq = [](const Customer& c1, const Customer& c2) {
return c1.number() == c2.number();
};
// create unordered set with user-defined behavior
std::unordered_set<Customer, decltype(hash), decltype(eq)> custset(10, hash, eq);
custset.insert(Customer("nico", "josuttis", 42));
PRINT_ELEMENTS(custset);
return 0;
}
/**
* [nico,josuttis,42]
*/
来源: Nicolai M. Josuttis 《The C++ Standard Library》
– 完 –