简体   繁体   English

C++ unordered_map 使用自定义 class 类型作为键

[英]C++ unordered_map using a custom class type as the key

I am trying to use a custom class as key for an unordered_map , like the following:我正在尝试使用自定义 class 作为unordered_map的键,如下所示:

#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;

class node;
class Solution;

class Node {
public:
    int a;
    int b; 
    int c;
    Node(){}
    Node(vector<int> v) {
        sort(v.begin(), v.end());
        a = v[0];       
        b = v[1];       
        c = v[2];       
    }

    bool operator==(Node i) {
        if ( i.a==this->a && i.b==this->b &&i.c==this->c ) {
            return true;
        } else {
            return false;
        }
    }
};

int main() {
    unordered_map<Node, int> m;    

    vector<int> v;
    v.push_back(3);
    v.push_back(8);
    v.push_back(9);
    Node n(v);

    m[n] = 0;

    return 0;
}

However, g++ gives me the following error:但是,g++ 给我以下错误:

In file included from /usr/include/c++/4.6/string:50:0,
                 from /usr/include/c++/4.6/bits/locale_classes.h:42,
                 from /usr/include/c++/4.6/bits/ios_base.h:43,
                 from /usr/include/c++/4.6/ios:43,
                 from /usr/include/c++/4.6/ostream:40,
                 from /usr/include/c++/4.6/iostream:40,
                 from 3sum.cpp:4:
/usr/include/c++/4.6/bits/stl_function.h: In member function ‘bool std::equal_to<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Node]’:
/usr/include/c++/4.6/bits/hashtable_policy.h:768:48:   instantiated from ‘bool std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_M_compare(const _Key&, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type, std::__detail::_Hash_node<_Value, false>*) const [with _Key = Node, _Value = std::pair<const Node, int>, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable.h:897:2:   instantiated from ‘std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node* std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_M_find_node(std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node*, const key_type&, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type) const [with _Key = Node, _Value = std::pair<const Node, int>, _Allocator = std::allocator<std::pair<const Node, int> >, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, _Hash = std::__detail::_Default_ranged_hash, _RehashPolicy = std::__detail::_Prime_rehash_policy, bool __cache_hash_code = false, bool __constant_iterators = false, bool __unique_keys = true, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node = std::__detail::_Hash_node<std::pair<const Node, int>, false>, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::key_type = Node, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable_policy.h:546:53:   instantiated from ‘std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type& std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::operator[](const _Key&) [with _Key = Node, _Pair = std::pair<const Node, int>, _Hashtable = std::_Hashtable<Node, std::pair<const Node, int>, std::allocator<std::pair<const Node, int> >, std::_Select1st<std::pair<const Node, int> >, std::equal_to<Node>, std::hash<Node>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, false, false, true>, std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type = int]’
3sum.cpp:149:5:   instantiated from here
/usr/include/c++/4.6/bits/stl_function.h:209:23: error: passing ‘const Node’ as ‘this’ argument of ‘bool Node::operator==(Node)’ discards qualifiers [-fpermissive]
make: *** [threeSum] Error 1

I guess, I need the tell C++ how to hash class Node , however, I am not quite sure how to do it.我想,我需要告诉 C++ 如何 hash class Node ,但是,我不太确定该怎么做。 How can I accomplish this tasks?我怎样才能完成这个任务?

To be able to use std::unordered_map (or one of the other unordered associative containers) with a user-defined key-type, you need to define two things:为了能够将std::unordered_map (或其他无序关联容器之一)与用户定义的键类型一起使用,您需要定义两件事:

  1. A hash function ;一个哈希函数 this must be a class that overrides operator() and calculates the hash value given an object of the key-type.这必须是一个覆盖operator()并计算给定键类型对象的哈希值的类。 One particularly straight-forward way of doing this is to specialize the std::hash template for your key-type.一种特别直接的方法是为您的键类型专门化std::hash模板。

  2. A comparison function for equality ;相等比较函数 this is required because the hash cannot rely on the fact that the hash function will always provide a unique hash value for every distinct key (ie, it needs to be able to deal with collisions), so it needs a way to compare two given keys for an exact match.这是必需的,因为散列不能依赖这样一个事实,即散列函数将始终为每个不同的键提供唯一的散列值(即,它需要能够处理冲突),因此它需要一种方法来比较两个给定的键精确匹配。 You can implement this either as a class that overrides operator() , or as a specialization of std::equal , or – easiest of all – by overloading operator==() for your key type (as you did already).您可以将其实现为覆盖operator()的类,或者作为std::equal ,或者 - 最简单的 - 通过为您的键类型重载operator==() (就像您已经做过的那样)。

The difficulty with the hash function is that if your key type consists of several members, you will usually have the hash function calculate hash values for the individual members, and then somehow combine them into one hash value for the entire object.散列函数的困难在于,如果您的键类型由多个成员组成,您通常会让散列函数计算各个成员的散列值,然后以某种方式将它们组合成整个对象的一个​​散列值。 For good performance (ie, few collisions) you should think carefully about how to combine the individual hash values to ensure you avoid getting the same output for different objects too often.为了获得良好的性能(即,很少发生冲突),您应该仔细考虑如何组合各个散列值,以确保避免频繁为不同的对象获得相同的输出。

A fairly good starting point for a hash function is one that uses bit shifting and bitwise XOR to combine the individual hash values.散列函数的一个相当好的起点是使用位移位和按位异或来组合各个散列值。 For example, assuming a key-type like this:例如,假设这样的键类型:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

Here is a simple hash function (adapted from the one used in the cppreference example for user-defined hash functions ):这是一个简单的哈希函数(改编自cppreference 示例中用于用户定义的哈希函数的函数):

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}

With this in place, you can instantiate a std::unordered_map for the key-type:有了这个,您可以为键类型实例化一个std::unordered_map

int main()
{
  std::unordered_map<Key,std::string> m6 = {
    { {"John", "Doe", 12}, "example"},
    { {"Mary", "Sue", 21}, "another"}
  };
}

It will automatically use std::hash<Key> as defined above for the hash value calculations, and the operator== defined as member function of Key for equality checks.它将自动使用上面定义的std::hash<Key>进行哈希值计算,并将operator==定义为Key成员函数进行相等性检查。

If you don't want to specialize template inside the std namespace (although it's perfectly legal in this case), you can define the hash function as a separate class and add it to the template argument list for the map:如果您不想在std命名空间内专门化模板(尽管在这种情况下它是完全合法的),您可以将哈希函数定义为一个单独的类并将其添加到映射的模板参数列表中:

struct KeyHasher
{
  std::size_t operator()(const Key& k) const
  {
    using std::size_t;
    using std::hash;
    using std::string;

    return ((hash<string>()(k.first)
             ^ (hash<string>()(k.second) << 1)) >> 1)
             ^ (hash<int>()(k.third) << 1);
  }
};

int main()
{
  std::unordered_map<Key,std::string,KeyHasher> m6 = {
    { {"John", "Doe", 12}, "example"},
    { {"Mary", "Sue", 21}, "another"}
  };
}

How to define a better hash function?如何定义更好的哈希函数? As said above, defining a good hash function is important to avoid collisions and get good performance.如上所述,定义一个好的散列函数对于避免冲突和获得良好的性能很重要。 For a real good one you need to take into account the distribution of possible values of all fields and define a hash function that projects that distribution to a space of possible results as wide and evenly distributed as possible.对于真正好的一个,您需要考虑所有字段的可能值的分布,并定义一个散列函数,将该分布投影到尽可能宽且分布均匀的可能结果空间。

This can be difficult;这可能很困难; the XOR/bit-shifting method above is probably not a bad start.上面的异或/位移方法可能不是一个糟糕的开始。 For a slightly better start, you may use the hash_value and hash_combine function template from the Boost library.对于一个稍微好一点的开始,您可以使用hash_valuehash_combine函数模板从Boost库。 The former acts in a similar way as std::hash for standard types (recently also including tuples and other useful standard types);前者的作用与标准类型的std::hash类似(最近还包括元组和其他有用的标准类型); the latter helps you combine individual hash values into one.后者可帮助您将单个哈希值合并为一个。 Here is a rewrite of the hash function that uses the Boost helper functions:这是使用 Boost 辅助函数的哈希函数的重写:

#include <boost/functional/hash.hpp>

struct KeyHasher
{
  std::size_t operator()(const Key& k) const
  {
      using boost::hash_value;
      using boost::hash_combine;

      // Start with a hash value of 0    .
      std::size_t seed = 0;

      // Modify 'seed' by XORing and bit-shifting in
      // one member of 'Key' after the other:
      hash_combine(seed,hash_value(k.first));
      hash_combine(seed,hash_value(k.second));
      hash_combine(seed,hash_value(k.third));

      // Return the result.
      return seed;
  }
};

And here's a rewrite that doesn't use boost, yet uses good method of combining the hashes:这是一个不使用 boost 的重写,但使用了组合散列的好方法:

namespace std
{
    template <>
    struct hash<Key>
    {
        size_t operator()( const Key& k ) const
        {
            // Compute individual hash values for first, second and third
            // http://stackoverflow.com/a/1646913/126995
            size_t res = 17;
            res = res * 31 + hash<string>()( k.first );
            res = res * 31 + hash<string>()( k.second );
            res = res * 31 + hash<int>()( k.third );
            return res;
        }
    };
}

I think, jogojapan gave an very good and exhaustive answer .我认为, jogojapan给出了一个非常好的和详尽的答案 You definitively should take a look at it before reading my post.在阅读我的帖子之前,您绝对应该先看看它。 However, I'd like to add the following:但是,我想添加以下内容:

  1. You can define a comparison function for an unordered_map separately, instead of using the equality comparison operator ( operator== ).您可以为unordered_map单独定义一个比较函数,而不是使用相等比较运算符 ( operator== )。 This might be helpful, for example, if you want to use the latter for comparing all members of two Node objects to each other, but only some specific members as key of an unordered_map .这可能会有所帮助,例如,如果您想使用后者来比较两个Node对象的所有成员,但只有一些特定成员作为unordered_map键。
  2. You can also use lambda expressions instead of defining the hash and comparison functions.您还可以使用lambda 表达式而不是定义散列和比较函数。

All in all, for your Node class, the code could be written as follows:总而言之,对于你的Node类,代码可以写成如下:

using h = std::hash<int>;
auto hash = [](const Node& n){return ((17 * 31 + h()(n.a)) * 31 + h()(n.b)) * 31 + h()(n.c);};
auto equal = [](const Node& l, const Node& r){return l.a == r.a && l.b == r.b && l.c == r.c;};
std::unordered_map<Node, int, decltype(hash), decltype(equal)> m(8, hash, equal);

Notes:笔记:

  • I just reused the hashing method at the end of jogojapan's answer, but you can find the idea for a more general solution here (if you don't want to use Boost).我只是在 jogojapan 的答案末尾重用了哈希方法,但是您可以在此处找到更通用的解决方案的想法(如果您不想使用 Boost)。
  • My code is maybe a bit too minified.我的代码可能有点过于缩小了。 For a slightly more readable version, please see this code on Ideone .有关可读性稍强的版本,请参阅Ideone 上的此代码

Most basic possible copy/paste complete runnable example of using a custom class as the key for an unordered_map (basic implementation of a sparse matrix):使用自定义类作为unordered_map键(稀疏矩阵的基本实现)的最基本的可能复制/粘贴完整的可运行示例:

// UnorderedMapObjectAsKey.cpp

#include <iostream>
#include <vector>
#include <unordered_map>

struct Pos
{
  int row;
  int col;

  Pos() { }
  Pos(int row, int col)
  {
    this->row = row;
    this->col = col;
  }

  bool operator==(const Pos& otherPos) const
  {
    if (this->row == otherPos.row && this->col == otherPos.col) return true;
    else return false;
  }

  struct HashFunction
  {
    size_t operator()(const Pos& pos) const
    {
      size_t rowHash = std::hash<int>()(pos.row);
      size_t colHash = std::hash<int>()(pos.col) << 1;
      return rowHash ^ colHash;
    }
  };
};

int main(void)
{
  std::unordered_map<Pos, int, Pos::HashFunction> umap;

  // at row 1, col 2, set value to 5
  umap[Pos(1, 2)] = 5;

  // at row 3, col 4, set value to 10
  umap[Pos(3, 4)] = 10;

  // print the umap
  std::cout << "\n";
  for (auto& element : umap)
  {
    std::cout << "( " << element.first.row << ", " << element.first.col << " ) = " << element.second << "\n";
  }
  std::cout << "\n";

  return 0;
}

For enum type, I think this is a suitable way, and the difference between class is how to calculate hash value.对于枚举类型,我认为这是一种合适的方式,而类之间的区别在于如何计算哈希值。

template <typename T>
struct EnumTypeHash {
  std::size_t operator()(const T& type) const {
    return static_cast<std::size_t>(type);
  }
};

enum MyEnum {};
class MyValue {};

std::unordered_map<MyEnum, MyValue, EnumTypeHash<MyEnum>> map_;

STL Does not provide hash function for pairs. STL 不提供对的散列函数。 You need to implement it yourself and either specify as template parameter or put into namespace std, from where it will be automatically picked up.您需要自己实现它并指定为模板参数或放入命名空间 std,从那里它会被自动拾取。 Following https://github.com/HowardHinnant/hash_append/blob/master/n3876.h is very useful for implementing custom hash functions for structutres.遵循https://github.com/HowardHinnant/hash_append/blob/master/n3876.h对于为结构实现自定义哈希函数非常有用。 More details are well explained in the other answers to this question, so I won't repeat that.更多细节在这个问题的其他答案中有很好的解释,所以我不会重复。 There is also similar thing ( hash_combine ) in the Boost. Boost 中也有类似的东西( hash_combine )。

check the following link https://www.geeksforgeeks.org/how-to-create-an-unordered_map-of-user-defined-class-in-cpp/ for more details.查看以下链接https://www.geeksforgeeks.org/how-to-create-an-unordered_map-of-user-defined-class-in-cpp/了解更多详情。

  • the custom class must implement the == operator自定义类必须实现 == 运算符
  • must create a hash function for the class (for primitive types like int and also types like string the hash function is predefined)必须为类创建一个散列函数(对于像 int 这样的原始类型和像 string 这样的类型,散列函数是预定义的)

The answers here were quite helpful, but I still struggled mightily trying to figure this out, so perhaps my lessons learned will be beneficial.这里的答案非常有帮助,但我仍在努力尝试解决这个问题,所以也许我的经验教训会有所帮助。 I had a bit of a unique situation compared to the OP;与 OP 相比,我的情况有点独特; my key was a custom UUID class that I didn't own .我的key我不拥有的自定义 UUID class。 In what I consider a bug/oversight, this class did not define a hash function, or overload for operator() (it did define the operator== , so I was set there).在我认为的错误/疏忽中,这个 class没有定义 hash function 或operator()的重载(它确实定义了operator== ,所以我被设置在那里)。 Yes, I had the source, but it was widely distributed and controlled, so modifying it was a non-starter.是的,我有源代码,但它被广泛分发和控制,所以修改它是行不通的。 I wanted to use this UUID as key in a std::unordered_map member, like我想将此 UUID 用作std::unordered_map成员中的键,例如

std::unordered_map<UUID, MyObject> mapOfObjs_;

In Visual Studio, I did eventually settle on this solution:在 Visual Studio 中,我最终选择了这个解决方案:

// file MyClass.h

namespace myNamespace
{
   static auto staticUuidHashFunc = [](const UUID& n)
   {
      // XORed the most and least significant bits, not important
   }
   ... 
   class MyClass
   {
      ...
   private:
      std::unordered_map<UUID, std::unique_ptr<MyObject>, decltype(staticUuidHashFunc)> mapOfObjs_;
   };
}

This worked great in Windows. However, when I finally took my code to gcc in linux, I got the warning (paraphrasing)这在 Windows 中非常有效。但是,当我最终在 linux 中将我的代码带到 gcc 时,我收到了警告(释义)

'MyClass' has a field 'mapOfObjs_' whose type uses the anonymous namespace 'MyClass'有一个字段'mapOfObjs_' ,其类型使用匿名命名空间

I even got this warning with all warnings disabled, so gcc must consider it quite serious.我什至在禁用所有警告的情况下收到此警告,因此 gcc 必须认为它非常严重。 I googled around and found this answer , which suggested I needed to move the hash function code to the.cpp file.我四处搜索并找到了这个答案,这表明我需要将 hash function 代码移动到 .cpp 文件。

At this point, I also tried deriving from the UUID class:此时,我还尝试从 UUID class 派生:

// file MyClass.h

namespace myNamespace
{
   struct myUuid : public UUID
   {
      // overload the operator()
   };
   ...
   // and change my map to use this type
   std::unordered_map<myUuid, std::unique_ptr<MyObject>> mapOfObjs_;
}

However, this brought its own set of problems.然而,这也带来了一系列问题。 Namely, all parts of code that used the (now parent) UUID class were incompatible with my map, like:即,使用(现在的父级) UUID class 的所有代码部分都与我的 map 不兼容,例如:

void MyClass::FindUuid(const UUID& id)
{
   // doesn't work, can't convert `id` to a `myUuid` type
   auto it = mapOfObjs_.find(id);
   ...
}

was now broken.现在坏了。 I didn't want to change all that code, so I punted on that and was back to the "put the code in the.cpp file" solution.我不想更改所有这些代码,所以我放弃了它并回到了“将代码放入 .cpp 文件”的解决方案。 However, stubbornly, I still tried a few things to keep the hash function in the.h file.然而,顽固地,我仍然尝试了一些方法来将 hash function 保留在 .h 文件中。 What I was really trying to avoid was dropping the auto from the hash function definition, since I didn't know and didn't want to have to figure out what the type was.我真正想要避免的是从 hash function 定义中删除auto ,因为我不知道也不想弄清楚类型是什么。 So I tried:所以我尝试了:

class MyClass
{
   ...
private:
   static auto staticUuidHashFunc = [](const UUID& n)
   {
      // my hash function
   }
};

But this (or variations of this) came back with errors, like "can't have static initializers in class", "cant use auto here", etc (I had a hard C++11 requirement).但是这个(或这个的变体)返回错误,比如“类中不能有 static 初始值设定项”,“不能在这里使用auto ”等(我有一个硬性的 C++11 要求)。 So I finally accepted I needed to treat this like a static variable, declare it in the header, and initialize it in.cpp file.所以我终于接受我需要像对待static变量一样对待它,在 header 中声明它,并在 .cpp 文件中初始化它。 Once I figured out its type, it was straight forward:一旦我弄清楚了它的类型,它就很简单了:

// MyClass.h
namespace myNamespace
{
   class MyClass
   {
      ...
   private:
      static std::function<unsigned long long(const UUID&)> staticUuidHashFunc;

      std::unordered_map<UUID, std::unique_ptr<MyObject>, decltype(staticUuidHashFunc)> mapOfObjs_;
   };
}

And finally in the.cpp file:最后在 .cpp 文件中:

// MyClass.cpp

namespace myNamespace
{
   std::function<unsigned long long(const UUID&)> MyClass::staticUuidHashFunc = [](const UUID& n)
    {
        // the hash function
    };

    MyClass::MyClass()
       : mapOfObjs_{ std::unordered_map<UUID, std::unique_ptr<MyObject>, decltype(staticUuidHashFunc)> (MyClass::NUMBER_OF_MAP_BUCKETS, staticUuidHashFunc)}
    {  }

   ...
}

Defining the static hash function in the.cpp file was the key.在.cpp文件中定义static hash function是关键。 After that, both Visual Studio and gcc were happy.之后,Visual Studio 和 gcc 都很开心。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM