【问题标题】:Keeping track of removed elements using std::remove_if使用 std::remove_if 跟踪删除的元素
【发布时间】:2012-03-05 08:18:27
【问题描述】:

我想从vector 中删除一些元素,并使用remove_if 算法来执行此操作。但是我想跟踪删除的元素,以便以后可以对它们执行一些操作。我尝试了以下代码:

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

using namespace std;


struct IsEven
{
    bool operator()(int n) 
    {
        if(n % 2 == 0)
        {
            evens.push_back(n);
            return true;
        }

        return false;
    }

    vector<int> evens;
};

int main(int argc, char **argv)
{

    vector<int> v;
    for(int i = 0; i < 10; ++i)
    {
        v.push_back(i);
    }

    IsEven f;
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
    for(vector<int>::iterator it = f.evens.begin(); it != f.evens.end(); ++it)
    {
        cout<<*it<<"\n";
    }

    v.erase(newEnd, v.end());

    return 0;
}

但这不起作用,因为remove_if 接受了我的仿函数对象的副本,因此无法访问存储的evens 向量。实现这一目标的正确方法是什么?

附: :这个例子,偶数和赔率只是为了举例,我的真实代码有些不同。因此,不要建议以不同方式识别偶数或赔率的方法。

【问题讨论】:

  • 你可以通过引用传递你的仿函数。使用boostc++11 的解决方案是否可以接受(使用ref 传递)?
  • 您可以在不涉及evens的情况下执行以下for(vector&lt;int&gt;::iterator it = newEnd; it != v.end(); ++it) { cout&lt;&lt;*it&lt;&lt;"\n"; } v.erase(newEnd, v.end());以获得正确的结果
  • @megabyte1024:不,那行不通。 newEnd 之外的元素不保证是被移除的元素。
  • @megabyte1024:不,你不能(但请参阅我的回答)。范围[newEnd, end) 包含未指定的值; remove_if 通常复制个匹配元素;它不会交换它们。

标签: c++ stl


【解决方案1】:

解决方案不是remove_if,而是表亲partial_sort partition。区别在于remove_if 只保证[begin, middle) 包含匹配的元素,而partition 也保证[middle, end) 包含与谓词不匹配的元素。

因此,您的示例变得只是(请注意,不再需要 evens):

vector<int>::iterator newEnd = partition(v.begin(), v.end(), f);
for(vector<int>::iterator it = newEnd; it != v.end(); ++it)
{
    cout<<*it<<"\n";
}
v.erase(newEnd, v.end());

【讨论】:

  • partial_sort 是否返回任何内容?这不在我的系统上编译。
  • 呃,我是怎么做到的?正在修复...我认为是斜视阅读。
  • 谢谢..我不知道这个..我只是颠倒了我的谓词条件,它现在运行良好..
【解决方案2】:

您最好的选择是std::partition(),它将重新排列序列中的所有 elt,例如您的谓词返回 true 的所有 elt 将位于返回 false 的所有 elt 之前。

示例:

vector<int>::iterator bound = partition (v.begin(), v.end(), IsEven);
std::cout << "Even numbers:" << std::endl;
for (vector<int>::iterator it = v.begin(); it != bound; ++it)
  std::cout << *it << " ";

std::cout << "Odd numbers:" << std::endl;
for (vector<int>::iterator it = bound; it != v.end(); ++it)
  std::cout << *it << " ";

【讨论】:

    【解决方案3】:

    如果您通过 reference 像这样传递 ist,则可以避免复制仿函数(即按值传递):

    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), 
       boost::bind<int>(boost::ref(f), _1));
    

    如果您不能使用boost,同样可以使用std::ref。我测试了上面的代码,它按预期工作。

    【讨论】:

    • +1,但请注意,boost::refstd::ref 的前身。它最初是在 boost 中引入的,然后是 TR1(很多编译器都将其命名为 std::tr1::ref,并最终在 C++11 中标准化。
    【解决方案4】:

    额外的间接级别。在本地声明向量,并且 让IsEven 包含它的副本。 IsEven 也可以 拥有向量,前提是它是由动态分配和管理的 shared_ptr。在实践中,我通常会找到局部变量 加指针解更方便。类似的东西:

    class IsEven
    {
        std::vector<int>* myEliminated;
    public:
        IsEven( std::vector<int>* eliminated = NULL )
            : myEliminated( eliminated )
        {
        }
        bool
        operator()( int n ) const
        {
            bool results = n % 2 == 0;
            if ( results && myEliminated != NULL ) {
                myEliminated->push_back( n );
            }
            return results;
        }
    }
    

    请注意,这也允许 operator()() 函数为 const。我 认为这是正式要求的(尽管我不确定)。

    【讨论】:

    • 如果它使用引用而不是指针,我会给它+1。当我必须为向量创建一个变量时,我可能会将其保留在普通旧数据中并使用初始化程序初始化局部变量。
    • @JanHudec 如果它使用引用,这是未定义的行为;功能对象必须是 CopyAssignable。离开它 POD 意味着你不能构造一个临时的:我的使用模式是声明向量,然后使用 IsEven( &amp;v ) 作为 remove_if 的参数。
    • 嗯,我找不到明确的声明它必须是可分配的,而不仅仅是 CopyConstructible,所以我现在有点困惑。引用显然是 CopyConstructible,但不是 Assignable。
    • @JanHudec 我找不到必须在 C++03 中的声明(但也许我没有找对地方)。尽管如此,由于它们是按值传递的,因此似乎指示了值语义,而值语义意味着复制和赋值。 (无论如何,我不会在不使其可分配的情况下公开复制可构造的东西。)
    • 我已经养成了让所有东西都参考的习惯,如果它不起作用,例如因为除了可复制之外它还需要可分配,我诅咒并更改它。这是因为指针是evil(引用比指针发生的事情更少,因此在使用引用读取代码时,您不必像使用指针读取代码时那样考虑那么多事情)。
    【解决方案5】:

    我在代码中看到的问题是,您在结构内创建的 evens 向量在每次 remove_if 算法调用它时都会创建。因此,无论您是否将仿函数传递给 remove_if ,它每次都会创建一个新向量。因此,一旦删除了最后一个元素,并且当函数调用结束并从函数中出来时, f.evens 将始终获取一个空向量。这可以通过两种方式排序,

    1. 用一个类替换结构并将 evens 声明为静态(如果这是你想要的)
    2. 或者你可以让 evens 全球化。我个人不建议这样做(这会使代码变得糟糕,除非你真的需要它们,否则对全局变量说不)。

    编辑:

    按照 nabulke 的建议,您也可以使用 std::ref 类似的东西,std::ref(f)。这可以防止您将向量设为全局并避免不必要的静态。

    使其全局化的示例如下,

    #include <vector>
    #include <algorithm>
    #include <iostream>
    
    using namespace std;
    vector<int> evens;
    
    struct IsEven
    {
        bool operator()(int n) 
        {
            if(n % 2 == 0)
            {
                evens.push_back(n);
                return true;
            }
    
            return false;
        }
    
    
    };
    
    int main(int argc, char **argv)
    {
    
        vector<int> v;
        for(int i = 0; i < 10; ++i)
        {
            v.push_back(i);
        }
    
        IsEven f;
        vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
        for(vector<int>::iterator it = evens.begin(); it != evens.end(); ++it)
        {
            cout<<*it<<"\n";
        }
    
        v.erase(newEnd, v.end());
    
        return 0;
    }
    

    这段代码对我来说似乎工作得很好。如果这不是您想要的,请告诉我。

    【讨论】:

    • 不会通过 std::ref 帮助传递函子吗?
    • @nabulke:嗯嗯嗯我猜是的。这可能会增加可能的解决方案。
    【解决方案6】:

    您可能有其他解决方案;仅当您不需要同时删除 elt 时(是吗?)。使用 std::for_each() 返回仿函数的副本。示例:

    IsEven result = std::for_each(v.begin(), v.end(), IsEven());
    
    // Display the even numbers.
    std::copy(result.evens.begin(), result.evens.end(), std::ostream_iterator<int> (cout, "\n"));  
    

    请注意,尽可能在 C++ 中创建未命名的变量总是更好。在这里,该解决方案并不能完全回答您的主要问题(从源容器中删除 elts),但它提醒大家 std::for_each() 返回您的仿函数的副本。 :-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-07
      • 1970-01-01
      • 1970-01-01
      • 2012-03-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多