Can we define hashcode method within a class in C+

2019-04-28 09:28发布

问题:

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


Note : In java , we can override the hashCode() method in the class and we could achieve this functionality. Once I override the hashCode() method, I need not worry about it later.

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.

回答1:

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.



回答2:

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++.



回答3:

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.



标签: c++ oop hashcode