c++c++11unique-ptrstdhash

std::hash for unique ptr in unordered map


I'm trying to hold a polymorphic type as a key in a map.

I came up with the following two structures:

Note that Game is an abstract class and the data structure I use is :

std::unordered_map<gamePtr,int> _allGames;

while gamePtr is a typedef for:

unique_ptr<Game>

template<>
struct std::hash<std::unique_ptr<Game>> {
  size_t operator()(std::unique_ptr<Game> game) const {
    return (std::hash<string>()(std::to_string(game->firstTeamFinalScore()) + game->firstTeam() + game->secondTeam()));
  }

};

struct cmp_games {
  bool operator() (std::unique_ptr<Game> game1, std::unique_ptr<Game> game2) const {  
    return *game1 == *game2;
  }
};

The cmp_games comparator seems to work fine but the std::hash does not because it tries to copy a unique_ptr (Which is ofc impossible) and I've no idea how to get over it. Would love to hear some suggestions (If that is even possible).

EDIT: The comparator also doesn't seem to work properly. how do I make this map work correctly with unique_ptr as a key?

EDIT2:

Came up with:

template<>
struct std::hash<std::unique_ptr<Game>> {
size_t operator()(const std::unique_ptr<Game>& game) const {
     return (std::hash<string>()(std::to_string(game->firstTeamFinalScore()) + game->firstTeam() + game->secondTeam()));
}
};

template<>
struct std::equal_to<std::unique_ptr<Game>> {
bool operator() (const std::unique_ptr<Game>& game1,const std::unique_ptr<Game>& game2) const {

    return *game1 == *game2;
}

};

Should they be enough?


Solution

  • The standard provides a specilization so that std::hash<unique_ptr<T>> is the same as std::hash<T*>. So provide a specialization for std::hash<Game *>. For example:

    #include <iostream>
    #include <memory>
    #include <unordered_map>
    #include <cstdlib>
    
    struct foo 
    {
        foo(unsigned i) : i(i) {}
        unsigned i;
    };
    
    namespace std {
    
    template<>
    struct hash<foo *>
    {
        size_t operator()(foo const *f) const
        {
            std::cout << "Hashing foo: " << f->i << '\n';
            return f->i;;
        }
    };
    
    }
    
    int main()
    {
        std::unordered_map<std::unique_ptr<foo>, int> m;
        m.insert(std::make_pair(std::unique_ptr<foo>(new foo(10)), 100));
        m.insert(std::make_pair(std::unique_ptr<foo>(new foo(20)), 200));
    }
    

    Live demo


    Another option is to change your existing std::hash specialization so that it takes the unique_ptr by reference.

    size_t operator()(std::unique_ptr<Game> const& game) const
    //                                      ^^^^^^ no more copying
    

    EDIT: std::unique_ptr provides comparison operators that compare the managed pointers. If you want the unordered_map to test the Game objects themselves for equality, provide an operator== overload instead of specializing std::equal_to

    inline bool operator==(const std::unique_ptr<Game>& game1, 
                           const std::unique_ptr<Game>& game2) 
    {
        return *game1 == *game2;
    }
    

    This, in turn, requires that you've provided an equality operator for Game (or you could just add the logic to the function above).

    inline bool operator==(Game const& game1, Game const& game2)
    {
        return // however you want to compare these
    }