如何使用以用户定义类型为键的std :: maps?


73

我想知道为什么不能将STL映射与用户定义的类一起使用。当我编译下面的代码时,我收到以下神秘的错误消息。这是什么意思?此外,为什么仅在用户定义的类型中发生这种情况?(将原始类型用作键时可以使用。)

C:\ MinGW \ bin .. \ lib \ gcc \ mingw32 \ 3.4.5 ........ \ include \ c ++ \ 3.4.5 \ bits \ stl_function.h ||在成员函数`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 |从`_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 |从此处实例化|

C:\ MinGW \ bin .. \ lib \ gcc \ mingw32 \ 3.4.5 ........ \ include \ c ++ \ 3.4.5 \ bits \ stl_function.h | 227 |错误:“ operator”不匹配<'in'__x <__y'| || ===构建完成:1个错误,0个警告=== |

#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;
}

Answers:


151

你不具备定义operator<为类,其实。您还可以为其创建比较器函数对象类,并使用它来专门化std::map。扩展您的示例:

struct Class1Compare
{
   bool operator() (const Class1& lhs, const Class1& rhs) const
   {
       return lhs.id < rhs.id;
   }
};

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

碰巧的std::mapstd::less,第三个模板参数的默认值为,它将operator<为您的类委派给定义的模板(如果没有,则失败)。但是有时您希望对象可用作映射键,但实际上却没有任何有意义的比较语义,因此,您不希望为此提供operator<类来使人们感到困惑。如果是这样,您可以使用上面的技巧。

实现此目的的另一种方法是专门化std::less

namespace std
{
    template<> struct less<Class1>
    {
       bool operator() (const Class1& lhs, const Class1& rhs) const
       {
           return lhs.id < rhs.id;
       }
    };
}

这样做的好处是它将被std::map“默认”选中,但是您不会暴露operator<给客户端代码。


5
我建议向这两个函数添加一个const关键字。
Diomidis Spinellis

也许值得friend少用该结构,否则我将其视为一种受损的封装。
天行者2012年

模板化的结构应以分号结尾,否则会出现编译错误。不幸的是,由于更改的字符数量少,我无法通过编辑来解决此问题
Ident 2015年

但是,为什么要放置结构较少的信息std?
Vladimir Tsyshnatiy '16

已经在std。这只是它的一种专业化。
帕维尔·米纳夫'16

29

默认情况下std::map(和std::set)用于operator<确定排序。因此,您需要operator<在您的课程上定义。

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

如果出于某种原因,您想要使用其他比较器,则map可以将的第三个模板参数更改std::greater为。


3
实际上,您可以将比较器更改为几乎任何两个参数的函数。
xtofl,2009年

17

您需要operator <为Class1定义。

Map需要使用运算符<比较值,因此,当将用户定义的类用作键时,您需要提供相同的值。

class Class1
{
public:
    Class1(int id);

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

1
它不需要operator <; 它只是默认设置。请参阅GMan或Pavel的答案。
xtofl,2009年

4
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;
    }


}

6
欢迎来到StackOverflow!请在回答中添加一些说明。
Aurasphere

3

键必须具有可比性,但是您尚未operator<为自定义类定义合适的键。


2

我想对Pavel Minaev的 答案进行一些扩展,在阅读我的答案之前应先阅读该答案。如果要比较的成员(例如,id在问题的代码中)是私有的,则Pavel提出的两种解决方案都不会编译。在这种情况下,VS2013为我抛出以下错误:

错误C2248:“ Class1 :: id”:无法访问在“ Class1”类中声明的私有成员

正如SkyWalker在对Pavel的回答的评论中提到的那样,使用声明会有所帮助。如果您想知道正确的语法,这里是:friend

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()用于id如下:

class Class1
{
public:
    Class1(int id) : id(id) {}
    int getId() const { return id; }

private:
    int id;
};

那么您可以使用它代替friend声明(即您进行比较lhs.getId() < rhs.getId())。从C ++ 11开始,您还可以将Lambda表达式用于Pavel的第一个解决方案,而不用定义比较器函数对象类。将所有内容放在一起,代码可能如下所示:

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

Ideone上的代码


1

正确的解决方案是std::less针对您的班级/结构进行专业培训。

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

  1. BST比较节点的元素以确定树的组织。
  2. 元素比较小于父节点的元素的节点位于父节点的左侧,元素比较大于父节点的元素的节点位于右侧。即

对于每个节点,node.left.key <node.key <node.right.key

BST中的每个节点都包含Elements,并且在映射的情况下,其KEY和一个值,并且应该对键进行排序。有关Map实现的更多信息: Map数据类型

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

因此,这意味着密钥应该与std::less或兼容,operator<以便可以进行组织。请检查地图参数

否则,如果您将用户定义的数据类型用作键,则需要给出该数据类型的完整比较语义。

解决方案:专长std::less

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

因此,std::less为您的用户定义的数据类型创建一个新 的。现在,默认情况下std::less将选择此新对象std::map

namespace std
{
    template<> struct  less<MyClass>
    {
        bool operator() (const MyClass& lhs, const MyClass& rhs) const
        {
            return lhs.anyMemen < rhs.age;
        }
    };

}

注意:您需要std::less为每种用户定义的数据类型创建专用的(如果要使用该数据类型作为cpp映射的键)。

错误的解决方案:用户定义的数据类型的 重载operator<。该解决方案也可以使用,但是非常糟糕,因为运算符<将为您的数据类型/类普遍过载。在客户端方案中这是不可取的。

请检查答案Pavel Minaev的答案

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.