Хеш-функция unordered_set
Я использую std::unordered_set
впервые и возник вопрос по хеш-функции. Насколько я понимаю, если вы не укажете хеш-функцию, по умолчанию будет использоваться std::hash.
У меня есть член mySet в одном из моих классов:
typedef std::unordered_set<MyClass> USetType;
USetType mySet;
Когда я пытаюсь собрать, я получаю сообщение об ошибке:
error C2440: 'type cast' : cannot convert from 'const MyClass' to 'size_t'
Нужно ли определять функцию преобразования (в size_t), если вы хотите использовать unordered_set с пользовательским классом? Есть ли способ избежать написания вашей собственной хэш-функции и просто использовать по умолчанию?
1 ответ
Если вы не укажете свой собственный хеш-функтор в качестве аргумента шаблона, по умолчанию будет std::hash<MyClass>
, который не существует, если вы не определите его.
Лучше определить свою собственную специализацию std::hash
внутри пространства имен std
:
namespace std {
template <>
struct hash<MyClass>
{
typedef MyClass argument_type;
typedef std::size_t result_type;
result_type operator()(const MyClass & t) const
{
/* ..calculate hash value for t */
}
};
}
И убедитесь, что вы включили этот код до объявления вашего хэша. Таким образом, вы можете объявить хеш просто как std::unordered_set<MyClass>
без необходимости дополнительных аргументов шаблона.
Вы не указали, что MyClass
выглядит как внутри, но типичная ситуация заключается в том, что ваш пользовательский тип просто состоит из нескольких членов простого типа, для которых существует хеш-функция по умолчанию. В этом случае вы, вероятно, захотите объединить значения хеш-функции для отдельных типов в значение хеш-функции для всей комбинации. Библиотека Boost предоставляет функцию под названием hash_combine
для этого. Конечно, нет гарантии, что он будет хорошо работать в вашем конкретном случае (это зависит от распределения значений данных и вероятности коллизий), но он обеспечивает хорошую и простую в использовании отправную точку.
Вот пример того, как его использовать, предполагая, MyClass
состоит из двух строковых членов:
#include <unordered_set>
#include <boost/functional/hash.hpp>
struct MyClass
{
std::string _s1;
std::string _s2;
};
namespace std {
template <>
struct hash<MyClass>
{
typedef MyClass argument_type;
typedef std::size_t result_type;
result_type operator()(const MyClass & t) const
{
std::size_t val { 0 };
boost::hash_combine(val,t._s1);
boost::hash_combine(val,t._s2);
return val;
}
};
}
int main()
{
std::unordered_set<MyClass> s;
/* ... */
return 0;
}
Я хотел бы подробнее остановиться на ответе Джогоджапана. Как упомянуто в комментарии CashCow к этому ответу, вы также должны перегрузить оператор сравнения равенства (operator==
) за MyClass
или определить отдельную функцию сравнения и предоставить ее unordered_set
, В противном случае вы получите другое сообщение об ошибке. Например, VS 2013 выбрасывает:
ошибка C2678: двоичный файл "==": не найден оператор, который принимает левый операнд типа "const MyClass" (или нет приемлемого преобразования)
Более того, вы можете использовать лямбда-выражения вместо определения хеш-функций и функций сравнения. Если вы не хотите использовать Boost, вы также можете создать хеш-функцию. Я понимаю, что вы хотите использовать некоторую функцию по умолчанию, но компилятор не знает, как вычислить значимый хэш для пользовательского класса. Тем не менее, вы можете использовать std::hash
для членов вашего класса. Если вы соберете все воедино, то ваш код может быть написан следующим образом:
class MyClass {
public:
int i;
double d;
std::string s;
};
int main()
{
auto hash = [](const MyClass& mc){
return (std::hash<int>()(mc.i) * 31 + std::hash<double>()(mc.d)) * 31 + std::hash<std::string>()(mc.s);
};
auto equal = [](const MyClass& mc1, const MyClass& mc2){
return mc1.i == mc2.i && mc1.d == mc2.d && mc1.s == mc2.s;
};
std::unordered_set<MyClass, decltype(hash), decltype(equal)> mySet(8, hash, equal);
return 0;
}