有比std::remove_if更好的方法从vector中删除元素吗?

30

std::vector 或其他容器中删除具有特定属性的元素的任务适合使用函数式风格的实现:为什么要费心用循环, 内存释放和正确地移动数据?

然而,在 C++ 中标准的做法似乎是使用以下惯用语:

std::vector<int> ints;
...
ints.erase(
    std::remove_if(ints.begin(), 
                   ints.end(),
                   [](int x){return x < 0;}),
    ints.end());

这个例子从整数向量中删除所有小于零的元素。

我认为这不仅难看,而且容易被错误使用。很明显,std::remove_if不能改变向量的大小(正如它的名称所暗示的那样),因为它只接受迭代器作为参数。但是,包括我在内的许多开发人员一开始并没有理解这一点。

那么有没有更安全、更优雅的方法来实现这一点呢?如果没有,为什么?


1
一如既往,如果变得复杂了,就将其封装在一个(模板)函数中。 - Karoly Horvath
@KarolyHorvath:是的,但这是一个非常常见的任务。我不想为此编写自己的函数。这应该像其他语言一样属于标准库。 - Frank Puffer
@KarolyHorvath:在Stack Overflow上提出这个问题可能是一个微小的第一步。比祈祷要好得多。 - Frank Puffer
@KarolyHorvath,这将在C++17中推出,详见我的回答。 - TemplateRex
1
在C++的字符串类中添加discard_space()或remove_space()会很好,这将为所有C++程序员节省100年的时间(浪费)。 - Kemin Zhou
显示剩余2条评论
2个回答

27

我觉得它不仅难看而且容易被使用错误。

别担心,我们一开始都会这样。

很明显,std::remove_if不能改变向量的大小(因为它的名字应该是这样的),因为它只获取迭代器。但许多开发人员,包括我自己,在开始时并没有意识到这一点。

一样。它会让每个人都感到困惑。或许在那些年里不应该称其为remove_if。 后见之明,嗯?

那么有没有更安全、更优雅的方法来实现这个目标呢?

没有。

如果没有,为什么?

因为这是最安全、最优雅的方法,可以在删除容器中的项时保留性能,从而使项无效化迭代器。

预测:

有什么我可以做的吗?

是的,将这个惯用法封装成一个函数。

template<class Container, class F>
auto erase_where(Container& c, F&& f)
{
    return c.erase(std::remove_if(c.begin(), 
                                  c.end(),
                                  std::forward<F>(f)),
                   c.end());    
}

激励性例子中的电话调用如下:

auto is_negative = [](int x){return x < 0;};
erase_where(ints, is_negative);
或者
erase_where(ints, [](int x){return x < 0;});

不用 remove_erase_if 吗? ;) - Yakk - Adam Nevraumont
2
@Yakk _if已经过时了。_where才是最新潮的。 - Richard Hodges
1
@Yakk 即将加入 LibFun2 http://en.cppreference.com/w/cpp/experimental/vector/erase_if - TemplateRex
"auto erase_where" 编译错误,提示“error: 'erase_where' function uses 'auto' type specifier without trailing return type”,因此我将其更改为 void 方法。 - Jayhello
你好Jay,或者将其更改为decltype(auto)。你使用哪个编译器? - Richard Hodges

19

这个将很快通过std::experimental::erase_if算法在C++17-ready编译器中提供:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <experimental/vector>

int main()
{
    std::vector<int> ints { -1, 0, 1 };   
    std::experimental::erase_if(ints, [](int x){
        return x < 0;
    });
    std::copy(ints.begin(), ints.end(), std::ostream_iterator<int>(std::cout, ","));
}

实时示例,打印0,1。


2
std::erase_if 在 C++20 中已经存在,定义在 <vector> 中。 - Lev Leontev
@LevLeontev,但它不允许使用迭代器begin,end,所以目前我们仍然只能使用remove_if或手动编写循环。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接