为什么std :: remove_copy_if()实际上没有删除?

时间:2012-08-13 04:12:27

标签: c++ stl

这可能是STL中命名最差的功能吗? (修辞问题)

std :: remove_copy_if()实际上似乎没有执行任何删除。我可以说,它的行为更像是copy_if_not。

否定有点令人困惑,但可以使用std :: not1(),但是我可能会误解某些东西,因为我无法理解这个函数与删除有什么关系 - 我错过了什么?

如果没有,是否有一个STL算法用于有条件地从容器中移除(移动?)元素&把它们放在另一个容器里?

编辑以添加示例,以便读者不那么困惑。

以下程序似乎保持输入范围(V1)不变:

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

using std::cout;
using std::endl;

int main (void)
{
    std::vector<int> V1, V2;
    V1.push_back(-2);
    V1.push_back(0);
    V1.push_back(-1);
    V1.push_back(0);
    V1.push_back(1);
    V1.push_back(2);

    std::copy(V1.begin(), V1.end(), std::ostream_iterator<int>(cout, " "));
    cout << endl;

    std::remove_copy_if(
        V1.begin(),
        V1.end(),
        std::back_inserter(V2),
        std::bind2nd(std::less<int>(), 0));

    std::copy(V2.begin(), V2.end(), std::ostream_iterator<int>(cout, " "));
    cout << endl;
    std::copy(V1.begin(), V1.end(), std::ostream_iterator<int>(cout, " "));
    cout << endl;
}

输出:

-2 0 -1 0 1 2 
0 0 1 2 
-2 0 -1 0 1 2 

我期待看到类似的东西:

-2 0 -1 0 1 2 
0 0 1 2 
0 0 1 2 ? ? ?

在哪里?可以是任何价值。但我很惊讶地看到输入范围没有受到影响,&amp;返回值不能与(在这种情况下)std :: vector :: erase()一起使用。 (返回值是输出迭代器。)

5 个答案:

答案 0 :(得分:20)

  

这可能是STL中命名最差的函数吗?

一些背景信息:在标准库(或原始STL)中,有三个概念,容器,进入这些容器的迭代器和应用于迭代器的算法。迭代器充当范围元素的游标和访问者,但没有对容器的引用(如前所述,甚至可能没有底层容器)。

这种分离有一个很好的功能,你可以将算法应用于不属于容器的元素范围(考虑像std::istream_iteratorstd::ostream_iterator这样的迭代器适配器)或属于容器的那些不要考虑所有元素(std::sort( v.begin(), v.begin()+v.size()/2 )来缩短容器的前半部分。)

负面的一面是,因为算法(和迭代器)并不真正知道容器,它们无法真正修改它,它们只能修改存储的元素(这是他们可以访问的)。像std::removestd::remove_if这样的变异算法可以在这个前提下工作:它们会有效地覆盖与条件不匹配的元素从容器中移除它们,但它们不会修改容器,只有包含的值,在 erase-remove 成语的第二步中由调用者决定:

v.erase( std::remove_if( v.begin(), v.end(), pred ),
         v.end() );

此外,对于变异算法(执行更改的算法),例如std::remove,有一个非变异版本,通过将copy添加到名称std::remove_copy_if来命名。没有XXXcopyYYY算法被认为是改变输入序列(尽管它们可以使用别名迭代器)。

虽然这不是std::remove_copy_if命名的借口,但我希望它有助于理解算法给出的名称:remove_if将修改内容范围并产生一个范围,与谓词匹配的所有元素都已被删除(返回的范围是由算法的第一个参数到返回的迭代器形成的范围)。 std::remove_copy_if执行相同操作,但不是修改基础序列,而是创建序列的副本,其中已删除与谓词匹配的元素。也就是说,所有* copy *算法等同于 copy ,然后应用原始算法(注意等价是逻辑的,std::remove_copy_if只需要 OutputIterator ,这意味着它无法复制,然后使用std::remove_if遍历复制的范围。

同样的推理可以应用于其他变异算法:reversereverse_copy范围内反转(记住,迭代器不访问容器)复制范围中的元素以按相反顺序分隔范围。

  

如果没有,是否有一个STL算法用于有条件地从容器中移除(移动?)元素&amp;把它们放在另一个容器里?

STL中没有这样的算法,但它很容易实现:

template <typename FIterator, typename OIterator, typename Pred>
FIterator splice_if( FIterator first, FIterator last, OIterator out, Pred p )
{
   FIterator result = first;
   for ( ; first != last; ++first ) {
      if ( p( *first ) ) {
         *result++ = *first;
      } else {
         *out++ = *first;
      }
   }
   return result;
}

答案 1 :(得分:4)

  

是否有STL算法用于有条件地从容器中移除(移动?)元素&amp;把它们放在另一个容器里?

我能想到的最接近的是std::stable_partition

std::vector<int> v;
// ...
auto it = std::stable_partition(v.begin(), v.end(), pick_the_good_elements);
std::vector<int> w(std::make_move_iter(it), std::make_move_iter(v.end()));
v.erase(it, v.end());

现在v将包含“好”元素,w将包含“坏”元素。

答案 2 :(得分:2)

  

如果没有,是否有一个STL算法用于有条件地从容器中移除(移动?)元素&amp;把它们放在另一个容器里?

不是真的。我们的想法是允许修改算法在容器中“移动”(不是在C ++意义上的单词)元素,但不能改变容器的长度。因此,remove算法可以称为prepare_for_removal

顺便说一句,C ++ 11提供了std::copy_if,它允许您将所选元素从一个容器复制到另一个容器,而无需使用remove_copy_if播放有趣的逻辑游戏。

答案 3 :(得分:1)

你是对的,就是它做的...... std :: remove_copy_if复制向量,删除任何与pred匹配的东西。

std :: remove_if ...在条件下移除(或者更确切地说,移动周围的东西)。

答案 4 :(得分:0)

我同意remove不是此系列功能的最佳名称。

但是,正如吕克所说的那样,它的工作原理是有道理的,而他提到的GoTW item解释了它是如何运作的。 remove_if的工作原理与删除完全相同 - 这就是您所期望的。

您可能还想阅读此Wikibooks article