删除特定元素的std :: erase和std :: remove组合不适用于特定示例

use*_*869 20 c++ stl erase-remove-idiom

#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector<int> a = {1,2,3,7,1,5,4};
    vector<int> b = {6,7,4,3,3,1,7};
    a.erase(remove(a.begin(),a.end(),a[0]),a.end());
    b.erase(remove(b.begin(),b.end(),b[0]),b.end());

    return 1;
}
Run Code Online (Sandbox Code Playgroud)

对于这个具体的例子,我的GNU gdb Ubuntu 7.7.1声明返回1行:a = {2,3,7,1,5,4}这是不期望的(只删除一个1),并且b = {7 ,4,3,3,1}这是不期望的.

我的期望是b应该是a = 2,3,7,5,4和b = 7,4,3,3,1,7.

这里发生了什么事?

tim*_*rau 19

宣言std::remove()看起来像

template <class ForwardIterator, class T>
  ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val);
Run Code Online (Sandbox Code Playgroud)

请注意,最后一个参数是引用.因此在编译之后它有效地传递了指定元素的地址.

通过remove(a.begin(), a.end(), a[0]),表示第0个元素的地址的东西a被传入.当remove()运行时,一旦处理了第0个元素,传入的引用指向的值就会改变,这会导致意外的结果.

要获得预期结果,请在致电前制作副本std::remove().

int toberemoved = a[0];
a.erase(remove(a.begin(),a.end(),toberemoved),a.end());
Run Code Online (Sandbox Code Playgroud)

  • 这也是我的第一反应.但是标准没有提出这个要求.这意味着`std :: remove`的实现必须采取必要的预防措施,如果他有你描述的问题,这是库中的错误.或者可能在标准中,因为可能意图是不应该要求它起作用. (5认同)
  • @StefanoSanfilippo您的链接显示它工作正常. (4认同)
  • 请不要传播由C++标准定义的抽象机器在引用和内存地址之间建立任何连接的神话. (4认同)
  • @JamesKanze我不确定它是否必须明确提出这个要求.`std :: remove`更改存储在各个迭代位置的值.此类更改会更改最后一个参数中引用的值.由于`std :: remove`是根据'==`指定的'value`,你告诉它更改value的值意味着如果`==`返回不同的东西,那就是调用者问题而不是算法.只要它执行有效的操作(即,它以稳定的方式从范围中删除元素),并且它调用`==`,`remove`实现遵循标准. (4认同)