I am trying to implement a class in C++, and I want each class to have its own implementation of hashcode(basically to use it as a key in unordered_map
& unordered_set
)
for eg:
class CustomClass{
int a;
vector<int> b;
string c;
bool operator ==(const CustomClass& o) const{
return ((a == o.a) && (b == o.b) && (c == o.c));
}
/*
Is it possible to define the hashcode function here instead of defining it outside the class.
size_t operator()() const {
// Some custom logic for calculating hash of CustomClass using
// the hash Values of its individual fields
std::size_t h = 0;
for(int& t : b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(a) << 1))^( std::hash<string>()(c) << 1);
}
*/
};
Now, suppose I want to use this in an unordered_map like
int main(){
unoredered_map<CustomClass, int> m;
}
I have two options,
i) Inject the hashcode in std namespace with Template specialization
namespace std {
template <> struct hash<CustomClass> {
size_t operator()(const CustomClass& o) const {
// Some custom logic for calculating hash of CustomClass using
// the hash Values of its individual fields
size_t h = 0;
for(int& t : o.b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(o.a) << 1))^( std::hash<string>()(o.c) << 1);
}
};
}
OR
ii.) Specify this function while creating the unordered_map
(or unordered_set
) every time while instantiating it, i.e
struct HashSpecialer {
std::size_t operator()(const CustomClass& o) const {
std::size_t h = 0;
for(int& t : o.b){
h = (h ^ std::hash<int>()(t)) << 1;
}
return (h^(std::hash<int>()(o.a) << 1))^( std::hash<string>()(o.c) << 1);
}
};
and while instantiating the unordered_map
, I have provide this struct.
int main(){
unoredered_map<CustomClass, int, HashSpecialer> m;
}
I find both the methods, confusing to use (i) Pollutes the std namespace and (ii) makes it hard by remembering to provide the HashSpecializer every time I instantiate an unordered_map
Is it possible to provide the hashcode function within the class definition itself , as I described in the commented section in the code snippet above
public class CustomClass {
int a;
List<Integer> b;
String c;
// I Let my IDE generate these methods :D
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CustomClass that = (CustomClass) o;
if (a != that.a)
return false;
if (b != null ? !b.equals(that.b) : that.b != null)
return false;
return c != null ? c.equals(that.c) : that.c == null;
}
// This one too :D
@Override public int hashCode()
{
int result = a;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (c != null ? c.hashCode() : 0);
return result;
}
}
I am looking for something like this as this proves to be very handy.
Hashcode is a unique code generated by the JVM at time of object creation. It can be used to perform some operation on hashing related algorithms like hashtable, hashmap etc. An object can also be searched with this unique code. Returns: It returns an integer value which represents hashCode value for this Method.
Java equals() and hashCode() methods are present in Object class. So every java class gets the default implementation of equals() and hashCode().
If your interface contains other methods, Java will complain if you don't implement the interface fully by providing an implementation of those methods. But in the case of equals() , hashCode() and toString() (as well as a few others that you didn't mention) the implementation already exists.
Both methods, equals() and hashcode() , are used in Hashtable , for example, to store values as key-value pairs. If we override one and not the other, there is a possibility that the Hashtable may not work as we want, if we use such object as a key.
I think the solution to your problem is to adjust your understanding of what is an aesthetically pleasing C++ program.
A specialization of std::hash
doesn't pollute the std namespace, instead you should consider that std::hash
is the customization point for controlling how unordered_map
works with your class.
Such a specialization is part of the interface of the class (and can be a friend of the class) in exactly the same way as binary operators like operator +()
should be a non-member functions, and still be part of the interface.
I am not sure how do I handle the situation if
CustomClass
is defined in more than one namespace
That's what namespaces are for. Your problem solves itself :)
namespace std {
template <> struct hash<NameSpace1::CustomClass> { ... };
template <> struct hash<NameSpace2::CustomClass> { ... };
}
C++ is not Java. Forget any Java you know, it'll help you tremendously. Lots of Java idioms look very different in C++, or are simply useless or inapplicable in C++.
C++'s design might be more verbose and cumbersome at a glance, but it's actually more flexible. All classes don't just have to know about hash codes; and different containers can require different hash constraints without intruding into the contained types at all.
That's what's so good about specialization. You can hook your code into the standard library without actually affecting it. After all, you're specializing for your own types, so it's not intrusive.
Specializing std::hash()
for your type is thus perfectly okay, so long as you do intend to use that as a reasonable default. Otherwise, you may want to consider an alias template for the combination of std::unordered_map
and your key type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With