C ++ 0x thêm vào hash<...>(...)
.
hash_combine
Mặc dù vậy, tôi không thể tìm thấy một chức năng như đã trình bày trong phần boost . Cách sạch nhất để thực hiện một cái gì đó như thế này là gì? Có lẽ, sử dụng C ++ 0x xor_combine
?
Câu trả lời:
Chà, cứ làm như những người tăng đã làm:
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);
}
std::pair
(hoặc tuple
, thậm chí). Nó sẽ tính toán hàm băm của mỗi phần tử, sau đó kết hợp chúng. (Và theo tinh thần của thư viện tiêu chuẩn, theo một cách triển khai được xác định.)
Tôi sẽ chia sẻ nó ở đây vì nó có thể hữu ích cho những người khác đang tìm kiếm giải pháp này: bắt đầu từ câu trả lời @KarlvonMoor , đây là phiên bản mẫu đa dạng, sẽ gọn gàng hơn trong cách sử dụng nếu bạn phải kết hợp nhiều giá trị với nhau:
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
Sử dụng:
std::size_t h=0;
hash_combine(h, obj1, obj2, obj3);
Điều này ban đầu được viết để triển khai macro thay đổi để dễ dàng làm cho các loại tùy chỉnh có thể băm (mà tôi nghĩ là một trong những cách sử dụng chính của một hash_combine
hàm):
#define MAKE_HASHABLE(type, ...) \
namespace std {\
template<> struct hash<type> {\
std::size_t operator()(const type &t) const {\
std::size_t ret = 0;\
hash_combine(ret, __VA_ARGS__);\
return ret;\
}\
};\
}
Sử dụng:
struct SomeHashKey {
std::string key1;
std::string key2;
bool key3;
};
MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3)
// now you can use SomeHashKey as key of an std::unordered_map
Điều này cũng có thể được giải quyết bằng cách sử dụng một mẫu đa dạng như sau:
#include <functional>
template <typename...> struct hash;
template<typename T>
struct hash<T>
: public std::hash<T>
{
using std::hash<T>::hash;
};
template <typename T, typename... Rest>
struct hash<T, Rest...>
{
inline std::size_t operator()(const T& v, const Rest&... rest) {
std::size_t seed = hash<Rest...>{}(rest...);
seed ^= hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
};
Sử dụng:
#include <string>
int main(int,char**)
{
hash<int, float, double, std::string> hasher;
std::size_t h = hasher(1, 0.2f, 2.0, "Hello World!");
}
Người ta chắc chắn có thể tạo một hàm mẫu, nhưng điều này có thể gây ra một số loại trừ khó chịu, ví dụ: hash("Hallo World!")
sẽ tính toán một giá trị băm trên con trỏ chứ không phải trên chuỗi. Đây có lẽ là lý do, tại sao tiêu chuẩn sử dụng cấu trúc.
Một vài ngày trước, tôi đã đưa ra phiên bản cải tiến một chút của câu trả lời này (yêu cầu hỗ trợ C ++ 17):
template <typename T, typename... Rest>
void hashCombine(uint& seed, const T& v, Rest... rest)
{
seed ^= ::qHash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hashCombine(seed, rest), ...);
}
Đoạn mã trên tốt hơn về mặt tạo mã. Tôi đã sử dụng hàm qHash từ Qt trong mã của mình, nhưng cũng có thể sử dụng bất kỳ hàm băm nào khác.
(int[]){0, (hashCombine(seed, rest), 0)...};
và nó cũng sẽ hoạt động trong C ++ 11.
Tôi thực sự thích cách tiếp cận C ++ 17 từ câu trả lời của vt4a2h , tuy nhiên nó gặp phải một vấn đề: Giá trị Rest
được truyền theo giá trị trong khi sẽ mong muốn hơn nếu chuyển chúng bằng tham chiếu const (đó là điều bắt buộc nếu phải sử dụng được với các loại chỉ di chuyển).
Đây là phiên bản đã điều chỉnh vẫn sử dụng biểu thức gấp (đó là lý do tại sao nó yêu cầu C ++ 17 trở lên) và sử dụng std::hash
(thay vì hàm băm Qt):
template <typename T, typename... Rest>
void hash_combine(std::size_t& seed, const T& v, const Rest&... rest)
{
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(hash_combine(seed, rest), ...);
}
Vì lợi ích hoàn chỉnh: Tất cả các loại có thể sử dụng được với phiên bản này của hash_combine
phải có chuyên môn hóa khuôn mẫu để hash
đưa vàostd
không gian tên.
Thí dụ:
namespace std // Inject hash for B into std::
{
template<> struct hash<B>
{
std::size_t operator()(B const& b) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h, b.firstMember, b.secondMember, b.andSoOn);
return h;
}
};
}
Vì vậy, kiểu đó B
trong ví dụ trên cũng có thể sử dụng được trong một kiểu khác A
, như ví dụ sử dụng sau đây cho thấy:
struct A
{
std::string mString;
int mInt;
B mB;
B* mPointer;
}
namespace std // Inject hash for A into std::
{
template<> struct hash<A>
{
std::size_t operator()(A const& a) const noexcept
{
std::size_t h = 0;
cgb::hash_combine(h,
a.mString,
a.mInt,
a.mB, // calls the template specialization from above for B
a.mPointer // does not call the template specialization but one for pointers from the standard template library
);
return h;
}
};
}
Hash
đối số mẫu của các vùng chứa tiêu chuẩn để chỉ định hàm băm tùy chỉnh của bạn thay vì đưa nó vào std
không gian tên.
Câu trả lời của vt4a2h chắc chắn là hay nhưng sử dụng biểu thức C ++ 17 gấp và không phải ai cũng có thể chuyển sang chuỗi công cụ mới hơn một cách dễ dàng. Phiên bản bên dưới sử dụng thủ thuật mở rộng để mô phỏng biểu thức gấp và hoạt động trong C ++ 11 và C ++ 14 .
Ngoài ra, tôi đã đánh dấu hàm inline
và sử dụng chuyển tiếp hoàn hảo cho các đối số mẫu khác nhau.
template <typename T, typename... Rest>
inline void hashCombine(std::size_t &seed, T const &v, Rest &&... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
(int[]){0, (hashCombine(seed, std::forward<Rest>(rest)), 0)...};
}