在C ++ STL映射上通过迭代器擦除

时间:2012-09-14 12:13:08

标签: c++ stl map iterator erase

我很好奇以下代码背后的基本原理。对于给定的地图,我可以使用以下代码删除最多但不包括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 );
对我来说似乎有点笨拙。另一种方法是重新编码,这样我可以使用按键式擦除重载,但如果我能帮助它,我宁愿不要重写太多。

3 个答案:

答案 0 :(得分:5)

关键是标准库中由两个迭代器确定的范围是半开范围。在数学符号[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之前测试迭代器,但是如果该测试在库内,则当用户知道迭代器有效时,将无法选择退出测试。

答案 1 :(得分:1)

n3337 23.2.4表102

a.erase( q1, q2)

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

因此,iterator返回的map::end()myMap.erase(myMap.end(), myMap.end())的情况下不在范围内;

a.erase(q)

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

  

我不会想到地图检查是否有困难   迭代器等于end(),在这种情况下不采取行动。在那儿   我错过了一些微妙的原因?

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

答案 2 :(得分:1)

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

当您使用erase(it)时,它会删除it指向的元素。 end()迭代器不指向有效元素,因此erase(end())没有做任何明智的事情。库有可能诊断出这种情况,并且调试库会这样做,但它会在每次调用erase时花费一大笔费用来检查迭代器指向的内容。标准库不会对用户施加成本。你是独自一人。 <g>