std :: remove with vector :: erase和undefined behavior

时间:2014-05-20 13:31:38

标签: c++ algorithm c++11 vector stl

在整个网络上,我看到人们使用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::removestd::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;
}

这似乎在我运行它的所有平台上按预期工作(不擦除任何东西,而不是segfaulting等),但我知道只是因为某些东西正在运行,并不代表它不是未定义的行为。

vector.erase的快速reference说明了这一点(强调我的):

iterator erase (const_iterator first, const_iterator last);

first, last

  

指定要移除的向量中的范围的迭代器:[first,last)。即,范围包括firstlast之间的所有元素,包括第一个指向的元素,但不包括last指向的元素。    成员类型iteratorconst_iterator是指向元素的随机访问迭代器类型。

vector.erase(vector.end(),vector.end())未定义的行为是什么?

以下是关于异常安全的快速参考说明:

  

如果删除的元素包含容器中的最后一个元素,则不会抛出异常(无抛出保证)。    否则,保证容器以有效状态结束(基本保证)。    无效的positionrange会导致未定义的行为。

所以,答案,至少对我来说似乎是&#34;是&#34;,而this StackOverflow answer似乎支持它。

因此,常见的习语错了吗?

假设它有未定义的行为,那么对remove的任何调用都可以返回vector.end()的迭代器,在调用vector.erase之前应该检查它,并在空向量上调用remove确实似乎返回vector.end :( IDEOne for code below

#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())

3 个答案:

答案 0 :(得分:28)

  

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),则该范围内没有元素,因此没有元素被删除。

答案 1 :(得分:5)

我认为你引用的更重要的是:

  

指定要移除的向量中的范围的迭代器:   [第一,最后一个)。即,该范围包括第一个之间的所有元素   最后,包括第一个而不是第一个指向的元素   最后指出。成员类型iterator和const_iterator是随机的   访问指向元素的迭代器类型。

正如我们在评论中发现的那样,来自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)所说的内容

  

大多数库的数据运算模板   结构具有使用范围的接口。范围是一对   指定计算开始和结束的迭代器。一个   范围[i,i]是一个空范围;通常,范围[i,j]指的是   数据结构中的元素以指向的元素开头   我和 但不包括j 指向的元素。范围[i,j)   当且仅当j可从i到达时才有效。结果   将库中的函数应用于无效范围是   未定义。

这样回答你的问题

  

但是下面的例子怎么样?

cplusplus.com在这种情况下是错误的

  

vector.erase(vector.end(),vector.end())未定义的行为是什么?

不,未触发任何未定义的行为。

  

因此,常见的习语错了吗?

不,这是对的。

  

实际的删除/删除成语应该是这个吗?

没有必要这样做,虽然也没关系。

答案 2 :(得分:4)

  

vector.erase(vector.end(),vector.end())未定义的行为是什么?

没有。因为你所使用的声明旁边的声明:

  

指定要移除的向量内的范围的迭代器:[first,last)。即,范围包括第一个和最后一个之间的所有元素,包括由第一个指向的元素,但不包括由最后一个指向的元素。

因此,vector.erase(vector.end(),vector.end())不会尝试删除vector.end(),因为参数last会指向它。

当然,这个定义含糊不清,这些陈述可以解释为矛盾。标准未使用引用的措辞。