std::remove 与 vector::erase 和未定义的行为
Posted
技术标签:
【中文标题】std::remove 与 vector::erase 和未定义的行为【英文标题】:std::remove with vector::erase and undefined behavior 【发布时间】:2014-07-08 19:15:00 【问题描述】:在整个网络上,我看到人们将erase/remove idiom 用于 C++ 向量,如下所示:
#include <vector> // the general-purpose vector container
#include <iostream>
#include <algorithm> // remove and remove_if
int main()
// initialises a vector that holds the numbers from 0-9.
std::vector<int> v = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ;
// removes all elements with the value 5
v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );
return 0;
也就是说,如果我想删除所有符合某些条件的元素(例如,int
s 向量中的数字 5),那么我将 std::remove
或 std::remove_if
与 vector.erase
结合使用,如下所示:
vector.erase( std::remove( vector.begin(), vector.end(), <some_value>), vector.end());
总的来说,这很好用; std::remove
(和 remove_if
)将复制(或在 C++11 中使用移动语义)要删除的元素到向量的末尾,因此我们之前示例中的向量现在看起来像这样:
0, 1, 2, 3, 4, 6, 7, 8, 9, 5 ;
元素 5 加粗,因为它已移到末尾。
现在,std::remove
将返回一个迭代器,然后我们在 erase
中使用它来清除元素。不错。
但是下面的例子呢?
int main()
// initialises an empty vector.
std::vector<int> v = ;
// removes all elements with the value 5
v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );
return 0;
这似乎在我运行它的所有平台上都按预期工作(不删除任何内容,不进行段错误等),但我知道仅仅因为某些东西在工作,并不意味着它不是未定义的行为。
vector.erase
的快速 reference 是这样说的(强调我的):
iterator erase (const_iterator first, const_iterator last);
first, last
是
在向量内指定范围的迭代器] 要删除:
[first,last)
。即范围包括first
和last
之间的所有元素,包括first指向的元素,但不包括last
指向的元素。 成员类型iterator
和const_iterator
是指向元素的随机访问迭代器类型。
vector.erase(vector.end(),vector.end())
是未定义的行为吗?
以下是快速参考中关于异常安全的内容:
如果移除的元素包括容器中的最后一个元素,则不会抛出异常(保证不抛出)。 否则,保证容器以有效状态结束(基本保证)。 无效的
position
或range
会导致未定义的行为。
所以,至少在我看来答案是“是”,this *** answer 似乎支持它。
所以,习语有错吗?
假设它是未定义的行为,那么任何对 remove
的调用都可能返回一个迭代器到 vector.end()
,在调用 vector.erase
之前应该检查它,并且在空向量上调用 remove 似乎返回 vector.end
: (@ 987654324@)
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
vector<int> myInts;
auto anIter = std::remove(myInts.begin(),myInts.end(),5);
if (anIter == myInts.end())
std::cout << "iterator = myInts.end()";
最后,我的问题:
实际的删除/擦除习语应该是这样吗?
auto endOfRangeIterator = std::remove(vector.begin(), vector.end(), <value>);
if (endOfRangeIterator != vector.end())
vector.erase(endOfRangeIterator, vector.end())
【问题讨论】:
不,空范围也可以。[first, first)
是一个空集。
@privatedatapublicchannel2:实际上,引用不正确。在first==last
的情况下,引用自相矛盾,指定一个元素被删除,但没有被删除。这就解释了为什么 AndyG 开始相信这是未定义的行为。
@privatedatapublicchannel2:不一样。标准实际上并没有说i
指向的元素包含在范围内。它只是说这是范围的开始。但是,它确实说j
指向的元素是not 包括在内的。此外,传递给erase
的第二个迭代器不一定是结束迭代器,所以如果first==last
,这并不一定意味着第一个指向结束。
@privatedatapublicchannel2:为了清楚起见,请阅读 cplusplus.com 的报价,将“last”替换为“first”。 "包括first指向的元素,但不包括first指向的元素"。在这种情况下,该陈述显然是矛盾的。
最终我们会erase_if 这将简化整个事情。
【参考方案1】:
24.2.1/7 库中大多数对数据结构进行操作的算法模板都有使用范围的接口。范围是一对 指定计算开始和结束的迭代器。 范围
[i,i)
是一个空范围;通常,范围[i,j)
指的是数据结构中以元素开头的元素i
指向的元素,但不包括指向的元素j
.
强调我的。
此外,您引用的erase
的描述不是标准中的规范文本。标准中有这样的说法(表 100):
a.erase(q1,q2)
效果:擦除 [q1, q2) 范围内的元素。
这并不要求q1
是可取消引用的。如果 [q1, q2) 是空范围(根据 24.2.1/7),则范围内没有元素,因此不会删除任何元素。
【讨论】:
感谢您的澄清。根据标准对空范围的定义确实有助于理清思路;否则我认为它会模棱两可。【参考方案2】:我认为你的引用中更重要的是:
指定向量内范围的迭代器] 被删除: [第一,最后)。即,范围包括第一个之间的所有元素 最后,包括第一个 指向的元素,但不是那个 由最后一个指出。成员类型 iterator 和 const_iterator 是随机的 访问指向元素的迭代器类型。
正如我们在 cmets 中发现的,来自cpluspluc.com 的引用是不正确的。这在( v.end, v.end)
的情况下不会违反规则,但在
#include <vector>
int main()
std::vector<int> v = 1, 2, 3 ;
v.erase( v.begin(), v.begin());
因为陈述自相矛盾
范围包括(...),包括所指向的元素 v.begin() 但不是 v.begin() 指向的那个。
不能是有效的陈述。
C++ Standard n3337 in § 23.2.2 序列容器要求表 100 指定
a.erase(q1,q2)
返回 iterator
。请注意:
要求:对于vector和deque,T应该是MoveAssignable。效果: 擦除 [q1, q2) 范围内的元素。
这就是 § 24.2.1/7 迭代器要求中关于[i,j)
范围的内容
大多数库的算法模板都对数据进行操作 结构具有使用范围的接口。范围是一对 指定计算开始和结束的迭代器。一种 range [i,i) 是一个空范围;一般来说,范围 [i,j) 指的是 数据结构中以指向的元素开头的元素 i 及以上 但不包括 j 所指向的元素。范围 [i,j) 当且仅当 j 可以从 i 到达时有效。结果 将库中的函数应用于无效范围是 未定义。
这样来回答你的问题
但是下面的例子呢?
cplusplus.com 在这种情况下是错误的
vector.erase(vector.end(),vector.end()) 也是未定义的行为吗?
不,不会触发未定义的行为。
所以,习语有错吗?
不,是正确的。
实际的删除/擦除习语应该是这样吗?
没有这个必要,虽然也可以。
【讨论】:
【参考方案3】:vector.erase(vector.end(),vector.end()) 也是未定义的行为吗?
没有。因为您强调的旁边的声明:
在向量中指定范围的迭代器] 被移除:[first,last)。即范围包括first和last之间的所有元素,包括first指向的元素但不包括last指向的元素。
因此,vector.erase(vector.end(),vector.end())
不会尝试擦除 vector.end()
,因为它是由参数 last
指向的。
当然,这个定义是模棱两可的,这些陈述可以被解释为相互矛盾的。标准未使用引用的措辞。
【讨论】:
以上是关于std::remove 与 vector::erase 和未定义的行为的主要内容,如果未能解决你的问题,请参考以下文章