3

我很好奇以下代码背后的基本原理。对于给定的地图,我可以使用以下代码删除一个范围,但不包括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 );

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

4

3 回答 3

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,但如果该测试在库中,那么当用户知道迭代器有效时,她将无法选择退出测试。

于 2012-09-14T12:39:56.770 回答
1

n3337 23.2.4 表 102

a.erase( q1, q2)

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

因此,如果iterator返回 frommap::end()不在范围内myMap.erase(myMap.end(), myMap.end());

a.erase(q)

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

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

原因是一样的,std::vector::operator[]不能检查,那个索引当然在范围内。

于 2012-09-14T12:18:05.790 回答
1

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

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

于 2012-09-14T12:27:45.280 回答