【问题标题】:How can I use std::maps with user-defined types as key?如何使用 std::maps 和用户定义的类型作为键?
【发布时间】:2010-11-09 07:10:07
【问题描述】:

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

C:\MinGW\bin..\lib\gcc\mingw32\3.4.5........\include\c++\3.4.5\bits\stl_function.h||In 成员函数`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|错误:不匹配对于 '__x

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

【问题讨论】:

标签: c++ dictionary stl containers stdmap


【解决方案1】:

您需要为 Class1 定义operator &lt;

Map 需要使用运算符

class Class1
{
public:
    Class1(int id);

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

【讨论】:

  • 不需要操作符
【解决方案2】:

默认情况下std::map(和std::set)使用operator&lt; 来确定排序。因此,你需要在你的类上定义operator&lt;

两个对象被视为equivalentif !(a &lt; b) &amp;&amp; !(b &lt; a)

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

【讨论】:

  • 事实上,您可以将比较器更改为大多数任何两个参数的函数。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    实际上,您没有为您的班级定义operator&lt;。您还可以为其创建一个比较器函数对象类,并使用它来专门化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&lt;(如果没有则失败)。但有时您希望对象可用作映射键,但实际上并没有任何有意义的比较语义,因此您不想通过在您的类上提供 operator&lt; 来混淆人们只是为了那。如果是这种情况,您可以使用上述技巧。

    实现同样目的的另一种方法是专门化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&lt; 暴露给客户端代码。

    【讨论】:

    • 我建议为这两个函数添加一个 const 关键字。
    • 也许值得 friend 使用 struct less 否则我认为它是一个妥协的封装。
    • 模板结构应该以分号结尾,否则会出现编译错误。不幸的是,由于更改的字符数量很少,我无法通过编辑来解决此问题
    • 但是你为什么要放 struct less info std?
    • 它已经在std 中。这只是其中的一个特化。
    【解决方案5】:
    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;
        }
    
    
    }
    

    【讨论】:

    • 欢迎来到 StackOverflow!请为您的答案添加一些解释。
    【解决方案6】:

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

    • cpp 中的地图基本上是作为二叉搜索树实现的。

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

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

    BST 中的每个节点都包含元素,在映射的情况下,它的 KEY 和一个值,并且键应该是有序的。 有关地图实施的更多信息:The Map data Type.

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

    所以这意味着密钥应该与std::lessoperator&lt; 兼容,以便可以组织它们。请查看map parameters

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

    解决方案:专攻std::less

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

    因此,为您的用户定义数据类型创建一个新的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&lt;。 这个解决方案也可以工作,但它非常糟糕,因为运算符&lt; 将针对您的数据类型/类普遍重载。这在客户端场景中是不可取的。

    请查看答案Pavel Minaev's answer

    【讨论】:

      【解决方案7】:

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

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

      正如 SkyWalkerPavel 的 回答中的 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() &lt; 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

      【讨论】:

        【解决方案8】:

        你的例子works in C++20如果你只是添加

        auto operator<=>(Class1 const &) const = default;
        

        到你的班级。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-07-25
          • 1970-01-01
          • 2017-04-27
          • 1970-01-01
          • 2014-08-13
          相关资源
          最近更新 更多