关于c ++:如何使用带有用户定义类型的std :: maps作为密钥?

How can I use std::maps with user-defined types as key?

我想知道为什么我不能将STL映射用于用户定义的类。当我编译下面的代码时,会收到下面的错误消息。这是什么意思?另外,为什么它只发生在用户定义的类型上?(当基元类型用作键时,它们是可以的。)

C:\MinGW\bin..\lib\gcc\mingw32\3.4.5........\include\c++\3.4.5\bits\stl_function.h||In
member function `bool
std::less<_Tp>::operator()(const _Tp&,
const _Tp&) const [with _Tp =
Class1]':|

C:\MinGW\bin..\lib\gcc\mingw32\3.4.5........\include\c++\3.4.5\bits\stl_map.h|338|instantiated
from `_Tp& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const _Key&) [with _Key = Class1, _Tp = int, _Compare = std::less, _Alloc = std::allocator >]'|

C:\Users\Admin\Documents\dev\sandbox\sandbox\sandbox.cpp|24|instantiated
from here|

C:\MinGW\bin..\lib\gcc\mingw32\3.4.5........\include\c++\3.4.5\bits\stl_function.h|227|error: no match for 'operator<' in '__x < __y'| ||=== Build finished: 1 errors, 0 warnings ===|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <map>

using namespace std;

class Class1
{
public:
    Class1(int id);

private:
    int id;
};

Class1::Class1(int id): id(id)
{}

int main()
{
    Class1 c1(1);

    map< Class1 , int> c2int;
    c2int[c1] = 12;

    return 0;
}


实际上,您不必为您的类定义operator<。您还可以为它创建一个Comparator函数对象类,并使用它来专门化std::map。要扩展示例:

1
2
3
4
5
6
7
8
9
struct Class1Compare
{
   bool operator() (const Class1& lhs, const Class1& rhs) const
   {
       return lhs.id < rhs.id;
   }
};

std::map<Class1, int, Class1Compare> c2int;

正好相反,std::map的第三个模板参数的默认值是std::less,它将委托给为您的类定义的operator<(如果没有,则失败)。但有时您希望对象可用作映射键,但实际上您没有任何有意义的比较语义,因此您不希望通过在类上提供operator<来混淆人们。如果是这样的话,你可以使用上面的技巧。

实现这一点的另一种方法是专门化std::less

1
2
3
4
5
6
7
8
9
10
namespace std
{
    template<> struct less<Class1>
    {
       bool operator() (const Class1& lhs, const Class1& rhs) const
       {
           return lhs.id < rhs.id;
       }
    };
}

这样做的好处是,它将由std::map"默认"选择,但是您不会向客户机代码公开operator<


默认情况下,std::mapstd::set使用operator<来确定排序。因此,您需要在类上定义operator<

两个对象被认为是等价的if !(a < b) && !(b < a)

如果出于某种原因,您想使用不同的比较器,那么可以将map的第三个模板参数更改为std::greater


您需要为Class1定义operator <

map需要使用operator<比较值,因此当用户定义的类用作键时,需要提供相同的值。

1
2
3
4
5
6
7
8
9
10
11
12
class Class1
{
public:
    Class1(int id);

    bool operator <(const Class1& rhs) const
    {
        return id < rhs.id;
    }
private:
    int id;
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class key
{
    int m_value;
public:
    bool operator<(const key& src)const
    {
        return (this->m_value < src.m_value);
    }

};
int main()
{
    key key1;
    key key2;
    map<key,int> mymap;
    mymap.insert(pair<key,int>(key1,100));
    mymap.insert(pair<key,int>(key2,200));
    map<key,int>::iterator iter=mymap.begin();
    for(;iter!=mymap.end();++iter)
    {
        cout<<iter->second<<endl;
    }


}


密钥必须是可比较的,但您还没有为自定义类定义合适的operator<


我想对帕维尔·米纳耶夫的答案做一点扩展,在阅读我的答案之前,你应该先阅读一下。如果要比较的成员(如问题代码中的id)是私有的,那么pavel提出的两个解决方案都不会编译。在这种情况下,VS2013为我抛出以下错误:

error C2248: 'Class1::id' : cannot access private member declared in class 'Class1'

正如天行者在关于帕维尔答案的评论中所提到的,使用friend声明是有帮助的。如果您想知道正确的语法,这里是:

1
2
3
4
5
6
7
8
9
10
class Class1
{
public:
    Class1(int id) : id(id) {}

private:
    int id;
    friend struct Class1Compare;      // Use this for Pavel's first solution.
    friend struct std::less<Class1>;  // Use this for Pavel's second solution.
};

Ideone代码

但是,如果您的私人成员具有访问功能,例如getId()for id如下:

1
2
3
4
5
6
7
8
9
class Class1
{
public:
    Class1(int id) : id(id) {}
    int getId() const { return id; }

private:
    int id;
};

然后您可以使用它而不是friend声明(即比较lhs.getId() < rhs.getId())。由于C++ 11,还可以使用lambda表达式来代替帕维尔的第一个解决方案,而不是定义一个比较器函数对象类。把所有东西放在一起,代码可以写成如下:

1
2
auto comp = [](const Class1& lhs, const Class1& rhs){ return lhs.getId() < rhs.getId(); };
std::map<Class1, int, decltype(comp)> c2int(comp);

Ideone代码


正确的解决方案是为您的类/结构专门化std::less

?基本上,cpp中的映射被实现为二进制搜索树。

  • BST比较节点的元素以确定树的组织。
  • 元素比较小于父节点的节点放置在父节点的左侧,元素比较大于父节点的节点放置在右侧。即
  • For each node, node.left.key < node.key < node.right.key

    BST中的每个节点都包含元素,如果映射了元素的键和值,则应该对键进行排序。关于映射实现的更多信息:映射数据类型。

    对于cpp映射,键是节点的元素,值不参与树的组织,它只是一个补充数据。

    因此,这意味着密钥应该与std::lessoperator<兼容,以便组织起来。请检查地图参数。

    否则,如果您使用用户定义的数据类型作为键,那么需要为该数据类型提供意义完整的比较语义。

    解决方案:专攻std::less

    地图模板中的第三个参数是可选的,它是std::less,将委托给operator<

    因此,为用户定义的数据类型创建一个新的std::less。现在,这个新的std::less将由std::map默认选择。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    namespace std
    {
        template<> struct  less<MyClass>
        {
            bool operator() (const MyClass& lhs, const MyClass& rhs) const
            {
                return lhs.anyMemen < rhs.age;
            }
        };

    }

    注意:您需要为每个用户定义的数据类型(如果您想将该数据类型用作CPP映射的键)创建专用的std::less

    不良解决方案:为用户定义的数据类型重载operator<。这个解决方案也可以工作,但它非常糟糕,因为对于您的数据类型/类,操作符<将普遍过载。这在客户端场景中是不可取的。

    请检查答案Pavel Minaev的答案