在C ++中,从列表中删除对象

时间:2010-10-20 01:27:02

标签: c++ stl remove-if

我正在写一个或多或少像这样的程序:

#include <list>

list<MyClass> things;

class MyClass {
   // some stuff

   void remove() {
       things.remove_if(THING_IS_ME);
   }
};

我需要写什么而不是THING_IS_ME?

换句话说,我正在使用全局STL列表作为事物的集合。在某些时候,列表中的一个对象会识别出它是多余的,并希望a)将自己从列表中删除,并且b)使自己被破坏。

我该怎么做?

我已经写了大约15年的C ++了,这个页面有点困惑:http://www.cplusplus.com/reference/algorithm/remove_if/

这些谓词是什么? C ++现在有更高阶的函数吗?

4 个答案:

答案 0 :(得分:5)

过去15年来,C ++的情况发生了巨大变化。 1994年7月,亚历山大·斯捷潘诺夫(Alexander Stepanov)提出的关于通用编程理念的图书馆的提议获得了ANSI / ISO委员会的最终批准。我们今天方便地称之为STL的库随后成为标准C ++库。 STL的故事与它背后的想法一样迷人,它绝对值得一读。

你发现的std::remove_if()函数只是这种哲学的另一种反映,它成为了C ++现代身份的一部分。简而言之,这是一个通用函数,可以处理任何元素的容器(序列)和任何(表现为a)的条件。为此,您必须提供两个功能:

  1. 一些迭代器,用于界定您希望处理的元素范围;
  2. 谓词,当在元素上调用时,如果要删除该元素,则返回true,否则返回false。
  3. 事实证明,在这种情况下,您想要的谓词是相等的谓词。并且因为基于相等性移除元素是如此常见的任务,所以标准还提供std::remove()函数,其假设隐式等式谓词。当然,您必须确保元素可以比较:

    bool operator==(const MyClass& a, const MyClass& b)
    {
        // return true if the two are equal, and false otherwise.
    }
    

    然后我们可以使用我们的谓词删除MyClass类型的元素:

    std::remove(things.begin(), things.end(), *this);  // if *this == elem
    

    回想一下,标准函数std::remove()适用于任何容器,即使是那些尚未创建的容器。因为每种容器都有自己的删除元素的方法,所以如果不知道它所使用的容器的实现细节,这个函数就无法真正执行删除。因此,std::remove()函数交换元素,使得“已删除”元素位于容器的末尾。然后,它返回一个迭代器,指向连续元素“removed”的第一个元素。

    typedef std::list<MyClass>::iterator iter;
    iter first_removed = std::remove(things.begin(), things.end(), *this);
    

    最后,我们通过调用特定容器的删除功能来真正删除元素,该功能适用​​于列表中的单个位置或要移除的一系列连续元素:

    things.erase(first_removed, things.end());
    

    在一行中看到这种代码并不罕见:

    things.erase(std::remove(things.begin(), things.end(), *this),
                 things.end());
    

    这一切似乎都是压倒性的和复杂的,但它有一些优点。首先,标准库的这种设计支持动态编程。它还允许标准库提供具有非常纤薄的接口的容器,以及几种可用于许多不同类型容器的自由功能。它允许您快速创建容器并立即获得标准库的所有功能以使用它。或者,它允许您快速编写一个通用函数,该函数可以立即与所有标准容器一起使用 - 那些已经写好的容器和那些尚未写入的容器。

答案 1 :(得分:2)

(最初是一组评论,但在发现OP实际想要做的事情之后重写为答案。)

您确实意识到STL容器存储了您插入的内容的副本,对吗?这意味着MyClass的实例更具可比性(例如,通过operator==) - 您不能只是比较地址,因为它们总是不同。

如果拥有MyClass的副本毫无意义,那么a pointer container可能会更好。

话虽这么说,C ++语言默认使用复制语义。该语言要求您在代码中明确指定引用。我强烈建议您选择a good C++ book,否则将来会被这样的问题绊倒。

答案 2 :(得分:0)

首先,一个简单的手写循环:

for( list<MyClass>::iterator it = things.begin(); it != things.end(); /*  */ ) {
    if( qualifiesForDelete( *it ) ) {
        it = things.erase( it );
    }
    else {
        ++it;
    }
}

其次,使用remove_if算法。作为算法的remove_iflist的成员函数相反,实际上不能删除任何元素,而是将要删除的元素移动到列表的末尾。随后必须调用erase。这是一个非常重要的习语,erase-remove idiom,必须学习。

things.erase( 
    remove_if( things.begin(), things.end(), deletionPredicate ), 
    things.end() 
);

其中deletionPredicate是一个函数或函数对象,它接受一个类型的参数并返回bool。返回true的元素被视为已删除。

答案 3 :(得分:0)

remove_if函数有三个参数:两个用于定义正在处理的范围的迭代器,以及一个谓词(返回bool的测试函数)。 start iterator指向你想要处理的第一个元素;结束迭代器指向范围中最后一个元素之后的元素。

page you linked to的示例中,谓词是代码行

bool IsOdd (int i) { return ((i%2)==1); }

为了让你的代码做你想做的事,你需要写这样的东西:

things.remove_if(things.begin(), things.end(), SomePredicateFunction);

并且您定义SomePredicateFunction就像这样(用适当的测试替换true):

bool SomePredicateFunction (MyClass c) { return true; }