【问题标题】:Why are there three comparisons to add a second map element?为什么要添加第二个地图元素要进行三个比较?
【发布时间】:2021-07-03 15:27:30
【问题描述】:

当我运行以下代码(注释掉一行)时,我没有得到任何输出(没有进行比较)。 但是在我取消注释最后一行的那一刻,我得到了三行输出(进行了三个比较)。添加第二项时,1-2 比较是可以理解的,但为什么要添加第三项呢?

#include <map>
#include <iostream>

using namespace std;

class MyType
{
  public :
  int a;
  MyType():a(0){}
};

bool operator<(const MyType &lhs, const MyType &rhs)
{
  cout<<"operator < is called\n";
  return lhs.a < rhs.a;
}

int main()
{
  MyType T1,T2;
  T1.a = 100;
  T2.a = 33; 
  
  map<MyType,int> Test;

  Test[T1] = 10; 
  //Test[T2] = 20; --> #Uncomment this line for getting 3 lines of "operator < is called"

}

未注释行的输出:

operator < is called
operator < is called
operator < is called

它是如何在只添加一个额外键的情况下从零开始打印三个字符串的?

MyCodeOnline

这里是lhsrhs的地址:

0x56365ea45e90
0x7ffe3c4cd47c
operator < is called
0x7ffe3c4cd47c
0x56365ea45e90
operator < is called
0x56365ea462d0
0x56365ea45e90
operator < is called

【问题讨论】:

  • 就像插入 USB 设备一样。尝试第一种方法失败,尝试第二种方法失败,再次尝试第一种方法并插入。
  • 当您将第一个项目添加到地图时,它是空的,没有什么可比较的。
  • 在我的系统 (clang-cl) 上,&lt; 运算符被调用了两次。我想这是实现定义的如何实现std::map 容器及其操作符。
  • @MarkRansom:是的,但是当添加第二项时,1-2 比较是可以的,但为什么第三项?
  • 在我看来,这与地图有关,而不是运算符重载。如果没有重载,将会有 3 次比较——您只是无法计算比较。

标签: c++ insert stdmap


【解决方案1】:

在 Visual Studio 调试版本中,它会在标准库中生成大量额外代码来捕获各种错误。

对于std::map,在插入新值后,它会检查比较器是否为comp(key, key) 正确返回false,因此实现所需的严格弱排序。

如果您切换到发布版本,则只有 2 个比较符合您的预期。

我推测 libstdc++ 中发生的事情是 Test[T2] = 20 必须执行四个操作:

  1. 检查T2是否已经存在
  2. T2 插入一个新元素
  3. 返回对新元素的引用
  4. 将新元素的值赋给20

我猜 libstdc++ 没有优化第 1 步和第 2 步来重用比较。如果您使用insert 将元素插入到映射中,则只执行 2 次比较(这在所有标准库中应该更有效,即使它们设法不产生对比较器的额外调用):

Test.insert(std::make_pair(T2, 20));

【讨论】:

  • 哪个版本的 gcc?
  • gcc 版本 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
  • 和g++ 11.1.0没区别:Demo on coliru
  • 使用宇宙飞船也不会改变事情:Demo on coliru。我只是出于好奇尝试过......
  • @AlanBirtles 事实上:std::insert() 少了一个:Demo on coliru
【解决方案2】:

我将这个答案基于使用 RB 树作为底层存储的映射的 libstdc++ gcc 实现。

当使用operator[] 插入地图时,它会做两件事:首先,它会查看是否可以在地图中找到现有元素,其次将元素添加/更新为新值。

  1. find操作是operator&lt;的第一次使用

  2. 下一个用途是插入底层 RB 树时。同样,第一个操作是检查新节点值是否等于树中已经存在的节点。

  3. 第二个操作是相对于前一个节点放置新节点的位置。

这与映射emplaceinsert 操作相反,后者不会执行find 操作,而是直接跳到将节点插入到底层树中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-27
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 2012-01-25
    相关资源
    最近更新 更多