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

时间:2009-07-09 07:42:28

标签: c++ stl maps

我想知道为什么我不能将STL地图用于用户定义的类。当我编译下面的代码时,我收到以下神秘的错误消息。这是什么意思?此外,为什么它只发生在用户定义的类型? (当它们用作密钥时,原始类型是可以的。)

  

C:\ MinGW的\ BIN .. \ lib中\ GCC \的mingw32 \ 3.4.5 ........ \包括\ C ++ \ 3.4.5 \比特\ 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 ........ \包括\ C ++ \ 3.4.5 \比特\ 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:\用户\管理员\文件\ dev的\沙箱\沙箱\ sandbox.cpp | 24 |实例化   从这里|

     

C:\ MinGW \ bin .. \ lib \ gcc \ mingw32 \ 3.4.5 ........ \ include \ c ++ \ 3.4.5 \ bits \ stl_function.h | 227 |错误:不匹配为'运营商<'在'__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;
}

7 个答案:

答案 0 :(得分:124)

实际上,你没有 为你的班级定义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::map的第三个模板参数的默认值为std::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<公开给客户端代码。

答案 1 :(得分:22)

默认情况下std::map(和std::set)使用operator<来确定排序。因此,您需要在班级上定义operator<

两个对象被视为equivalent if !(a < b) && !(b < a)

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

答案 2 :(得分:12)

您需要为Class1定义operator <

Map需要使用operator&lt;来比较值。因此,当用户定义的类用作密钥时,您需要提供相同的内容。

class Class1
{
public:
    Class1(int id);

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

答案 3 :(得分:3)

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

答案 4 :(得分:3)

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


}

答案 5 :(得分:0)

正确的解决方案是为您的班级/结构专长std::less

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

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

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

BST中的每个节点都包含Elements,并且在映射的情况下,其KEY和一个值以及键应该被排序。 有关地图实施的更多信息:The Map data Type

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

因此,这意味着密钥应与std::lessoperator<兼容,以便可以进行组织。请检查map parameters

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

解决方案:专长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's answer

答案 6 :(得分:0)

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

  

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

SkyWalker Pavel的答案的comments中提到,使用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.
};

Code on 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 expression用于 Pavel的第一个解决方案,而不用定义比较器函数对象类。 将所有内容放在一起,代码可能如下所示:

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

Code on Ideone