【问题标题】:Erase by iterator on a C++ STL map通过 C++ STL 映射上的迭代器擦除
【发布时间】:2012-09-14 12:13:08
【问题描述】:

我很好奇以下代码背后的基本原理。对于给定的地图,我可以使用以下代码删除最多但不包括end()(显然)的范围:

map<string, int> myMap;
myMap["one"] = 1;
myMap["two"] = 2;
myMap["three"] = 3;

map<string, int>::iterator it = myMap.find("two");

myMap.erase( it, myMap.end() );

这会删除使用范围的最后两项。但是,如果我使用单一迭代器版本的擦除,我有一半希望通过 myMap.end() 不会导致任何操作,因为迭代器显然位于集合的末尾。这与明显会导致未定义行为的损坏或无效迭代器不同。

但是,当我这样做时:

myMap.erase( myMap.end() );

我只是遇到了分段错误。我不会认为 map 很难检查迭代器是否等于 end() 并且在这种情况下不采取行动。我错过了一些微妙的原因吗?我注意到即使这样也有效:

myMap.erase( myMap.end(), myMap.end() );

(即什么都不做)

我问的原因是我有一些代码接收到集合的有效迭代器(但可能是end()),我想简单地将它传递给擦除,而不是像这样首先检查:

if ( it != myMap.end() )
    myMap.erase( it );

这对我来说似乎有点笨拙。另一种方法是重新编码,这样我就可以使用按键类型擦除重载,但如果我能提供帮助,我宁愿不要重写太多。

【问题讨论】:

  • @JoachimPileborg:谢谢,这是合乎逻辑的。由于这个原因,传递一个有效的 - 非 end() - 迭代器两次导致没有擦除。单迭代器版本仍然让我感到困惑。
  • 单迭代器版本需要一个可解引用的迭代器,即实际引用一个可以擦除的元素的迭代器。过去的迭代器是不可取消引用的,所以如果你试图擦除那个不存在的元素,你会得到未定义的行为。
  • @MikeSeymour:谢谢。是否有特定原因必须取消引用?另外,你能告诉我你在哪里找到的吗?我看到的所有在线参考资料都没有提到这个事实。
  • 一般来说,erase 的实现可能很昂贵或不可能判断任何特定的迭代器是否可解除引用,因此将负担放在调用者身上。它在 C++11 标准表 100 中指定;它定义了a.erase(q) 的行为,其中前一段 23.2.3/3 指定“q 表示对a 的有效可解引用常量迭代器”。
  • 虽然,测试一个迭代器一般是否可解引用是一个比测试它是否等于end()更难的问题。对于大多数(所有?)容器,结束迭代器是唯一不可取消引用的有效迭代器,困难的部分是跟踪迭代器是否有效。所以提问者的提议比一般的检查迭代器的负担要小。

标签: c++ stl map iterator erase


【解决方案1】:

关键是在标准库中,由两个迭代器确定的范围是半开范围。在数学符号中[a,b) 它们包括第一个但不包括最后一个迭代器(如果两者相同,则范围为空)。同时,end() 返回一个迭代器,它比最后一个元素多了一个,它与半开范围表示法完美匹配。

当您使用erase 的范围版本时,它永远不会尝试删除最后一个迭代器引用的元素。考虑一个修改后的例子:

map<int,int> m;
for (int i = 0; i < 5; ++i)
   m[i] = i;
m.erase( m.find(1), m.find(4) );

在执行结束时,地图将包含两个键 04。请注意,第二个迭代器引用的元素没有从容器中删除。

另一方面,单个迭代器操作将擦除迭代器引用的元素。如果上面的代码改成:

for (int i = 1; i <= 4; ++i ) 
   m.erase( m.find(i) );

键为4 的元素将被删除。在您的情况下,您将尝试删除未引用有效对象的结束迭代器。

我不会认为 map 很难检查迭代器是否等于 end() 并且在这种情况下不采取行动。

不,这并不难,但该函数的设计考虑了不同的契约:调用者必须将迭代器传递给容器中的元素。造成这种情况的部分原因是,在 C++ 中,大多数功能的设计都是为了尽可能降低成本,从而允许用户在安全/性能方面取得平衡。用户可以在调用 erase 之前测试迭代器,但如果该测试在库中,那么当用户知道迭代器有效时,她将无法选择退出测试。

【讨论】:

    【解决方案2】:

    n3337 23.2.4 表 102

    a.erase( q1, q2)
    

    删除 范围 [q1,q2) 中的所有元素。返回 q2。

    所以,从map::end()返回的iterator不在myMap.erase(myMap.end(), myMap.end())的范围内;

    a.erase(q)
    

    擦除 q 指向的元素。返回一个迭代器,该迭代器指向紧跟在 q 之后的元素,在该元素被擦除之前。如果不存在这样的元素,则返回 a.end()。

    我不会认为 map 很难检查 迭代器等于 end() 并且在这种情况下不采取行动。有没有 我错过了一些微妙的原因?

    原因相同,std::vector::operator[] 不能检查,索引当然在范围内。

    【讨论】:

    • 据我所知,map::erase() 的单迭代器版本会返回 void,除非您说的是 C++11(我不是。)
    • @Component10 我链接了 n3337 草案中的引用,所以,是的,它是关于 C++11 的。
    【解决方案3】:

    当你使用两个迭代器来指定一个范围时,范围由第一个迭代器指向的元素到不包括第二个迭代器指向的元素的元素组成。所以erase(it, myMap.end()) 说要删除从it 到但不包括end() 的所有内容。您同样可以将指向“真实”元素的迭代器作为第二个传递,并且该迭代器指向的元素不会被删除。

    当您使用erase(it) 时,它表示要删除it 指向的元素。 end() 迭代器没有指向一个有效的元素,所以 erase(end()) 没有做任何明智的事情。库可以诊断这种情况,调试库会这样做,但是每次调用 erase 检查迭代器指向的内容都会产生成本。标准库不会将这种成本强加给用户。你自己一个人。 &lt;g&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-02
      • 2016-09-03
      • 2011-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-23
      • 2011-05-01
      相关资源
      最近更新 更多