使用 erase-remove_if 成语

Using erase-remove_if idiom

假设我有 std::vector<std::pair<int,Direction>>.

我正在尝试使用 erase-remove_if 习语从向量中删除对。

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool { return stopPoint.first == 4; }));

我想删除所有将 .first 值设置为 4 的对。

在我的示例中,我有对:

- 4, Up
- 4, Down
- 2, Up
- 6, Up

然而,在我执行 erase-remove_if 之后,我剩下:

- 2, Up
- 6, Up
- 6, Up

我做错了什么?

正确的代码是:

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool 
                                       { return stopPoint.first == 4; }), 
                 stopPoints.end());

您需要删除从std::remove_if返回的迭代器开始到向量末尾的范围,而不仅仅是单个元素。

“为什么?”

  • std::remove_if 交换向量内的元素,以便将 所有与谓词不匹配的元素放在容器 的开头。这意味着如果谓词(lambda 函数的主体)returns true,则该元素将放置在向量的 end 处。

  • remove_if 然后 **returns 一个指向第一个与谓词匹配的元素的迭代器 **。换句话说,指向要删除的第一个元素的迭代器。

  • std::vector::erase 擦除从返回的迭代器开始到向量结尾的范围,这样所有元素匹配谓词被删除.


更多信息: Erase-remove idiom (Wikipedia).

方法 std::vector::erase 有两个重载:

iterator erase( const_iterator pos );
iterator erase( const_iterator first, const_iterator last );

第一个仅删除 pos 处的元素,而第二个删除范围 [first, last).

由于您在调用中忘记了 last 迭代器,因此第一个版本由重载决议选择,并且您只删除了移动到末尾 std::remove_if 的第一对。您需要这样做:

stopPoints.erase(std::remove_if(stopPoints.begin(),
                                stopPoints.end(),
                                [&](const stopPointPair stopPoint)-> bool { 
                                    return stopPoint.first == 4; 
                                }), 
                 stopPoints.end());

erase-remove 习语的工作原理如下。假设你有一个向量 {2, 4, 3, 6, 4} 并且你想删除 4:

std::vector<int> vec{2, 4, 3, 6, 4};
auto it = std::remove(vec.begin(), vec.end(), 4);

将通过将“删除的”值放在末尾来将向量转换为 {2, 3, 6, A, B}(末尾的值 AB 未指定(就好像该值是moved),这就是为什么你的例子中有 6) 和 return 到 A 的迭代器(“已删除”值的第一个).

如果你这样做:

vec.erase(it)

...选择 std::vector::erase 的第一个重载,您只删除 it 处的值,即 A 并得到 {2, 3, 6, B}.

通过添加第二个参数:

vec.erase(it, vec.end())

...选择第二个重载,您擦除 itvec.end() 之间的值,因此 AB 都被擦除。