【问题标题】:A C++ STL program using functor as predicate一个使用函子作为谓词的 C++ STL 程序
【发布时间】:2012-04-22 12:31:04
【问题描述】:

在下面的 C++ STL 程序中,我定义了一个函子第 N 次,如果它在第 n 次被撤销,则返回 true。然后我将其转换为通用算法 remove_if,我得到了一些奇怪的东西。

代码:

#include <iostream>
#include <list>
#include <algorithm>
#include "print.hpp"

using namespace std;

class Nth{
private:
    int nth,ncount;
public:
    Nth(int n):nth(n),ncount(0){}

    bool operator()(int)
    {
        return ++ncount == nth;
    }
};

int main()
{
    list<int> col;
    for (int i = 1;i <=9 ;++i)
    {
        col.push_back(i);
    }

    PRINT_ELEMENTS(col,"col : ");

    list<int>::iterator pos;
    pos = remove_if(col.begin(),col.end(),
        Nth(3));

    col.erase(pos,col.end());

    PRINT_ELEMENTS(col,"nth removed : ");
}

print.hpp:

#include <iostream>

template <class T>
inline void PRINT_ELEMENTS (const T& coll, const char* optcstr="")
{
    typename T::const_iterator pos;

    std::cout << optcstr;
    for (pos=coll.begin(); pos!=coll.end(); ++pos) {
        std::cout << *pos << ' ';
    }
    std::cout << std::endl;
}

我在 Microsoft Visual Studio 2008 中运行它,我得到了结果: 它删除了我不想要的元素 3 和 6。我以为只有 3 会被删除。 有人可以帮我翻译吗?非常感谢。

【问题讨论】:

    标签: c++ generics stl


    【解决方案1】:

    来自 C++ 标准库:Nicolai M. Josuttis 的教程和参考

    发生这种情况是因为算法的通常实现在算法期间在内部复制谓词:

    template <class ForwIter, class Predicate>
       ForwIter std::remove_if(ForwIter beg, ForwIter end,
                               Predicate op)
       {
           beg = find_if(beg, end, op);
           if (beg == end) {
               return beg;
           }
           else {
           ForwIter next = beg;
               return remove_copy_if(++next, end, beg, op);
           }
       }
    

    该算法使用 find_if() 来查找应删除的第一个元素。但是,它然后使用传递的谓词 op 的副本来处理剩余的元素(如果有)。这里再次使用原始状态的第 N 个元素,同时移除剩余元素中的第三个元素,实际上是第六个元素。

    此行为不是错误。该标准没有指定算法在内部复制谓词的频率。因此,为了获得 C++ 标准库的保证行为,您不应传递行为取决于复制或调用频率的函数对象。因此,如果您为两个参数调用一元谓词并且两个参数相等,则谓词应该始终产生相同的结果。也就是说,谓词不应因调用而改变其状态,并且谓词的副本应具有与原始状态相同的状态。为确保不会因为函数调用而改变谓词的状态,应将 operator() 声明为常量成员函数。

    【讨论】:

    • 更准确地说,OP希望实现的目标仍然是可能的。状态应该被外部化并以可变引用的形式传递给谓词。然后给定谓词的所有副本将共享与原始谓词相同的可变状态。
    【解决方案2】:

    不要在std::list 上使用std::remove_if。相反,使用列表的成员函数:

    col.remove_if(Nth(3));
    

    通用算法会重新排列元素的值,以便您可以安全地从末尾擦除,但对于列表,成员算法会直接删除不需要的 节点,而不会触及任何其他元素。

    更新。 正如所指出的,这实际上并不能保证解决您的问题,因为您的谓词不允许具有内部按值状态。试试这个:

    struct Nth
    {
        const int n;
        int & counter;
        Nth(int N, int & c) : n(N), counter(c) { }
        bool operator()(int) const { return ++counter == N; }
    };
    
    {
        int counter = 0;
        cols.remove_if(Nth(3, counter));
    }
    

    这个新的谓词是可复制的,并充当(外部)计数器变量的引用包装器。

    【讨论】:

    • 这并不能保证只使用谓词的一个副本,就像std::remove_if 一样。如果它确实解决了这个问题,那只是偶然。
    【解决方案3】:

    我看了《The Standard C++ Library》,找到了另一种解决方案,那就是:重新实现函数remove_if:

    template <class ForwIter,class Predicate>
    ForwIter remove_if_re(ForwIter begin,ForwIter end,Predicate op)
    {
        while(begin != end && !op(*begin))
            ++begin;
        if(begin == end)
            return begin;
        else{
            ForwIter next = begin;
            return remove_copy_if(++next,end,begin,op);
        }
    }
    

    确实有效。

    但我有点好奇。这个实现不使用传递的谓词操作的副本来处理剩余的元素吗???

    我是学习 STL 的新手。感谢您耐心的回答。

    非常感谢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-22
      • 2011-08-30
      • 1970-01-01
      相关资源
      最近更新 更多