2012-04-22 58 views
7

在下面的C++ STL程序中,我定義了一個函子Nth,如果它在第n次被撤銷,它將返回true。並且我將它轉換爲通用算法remove_if,奇怪。使用函子作爲謂詞的C++ STL程序

代碼:

#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中運行它,我得到的結果是: enter image description here 它刪除元件(3)和6不是我想要的。我以爲只有3個會被刪除。 有人可以爲我解釋嗎?非常感謝。

回答

11

從C++標準庫:教程和參考由尼古拉M.約祖蒂斯

這是因爲在算法中常用的實現算法副本的謂詞內部:

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()聲明爲常量成員函數。

+1

更確切地說,OP希望實現的目標仍然是可能的。該狀態應該被外化並以可變參考的形式傳遞給謂詞。然後,給定謂詞的所有副本將共享與原始狀態相同的可變狀態。 – 2012-04-22 14:28:45

5

請勿在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)); 
} 

這個新的謂詞是可複製的,並作爲你的(外部)計數器變量的引用包裝。

+0

這並不能保證僅使用謂詞的單個副本,不會超過'std :: remove_if'。如果它似乎解決了這個問題,那麼這只是偶然。 – 2012-04-22 12:55:42

+0

@MikeSeymour:是的,你說得對。我會添加一個註釋。 – 2012-04-22 13:01:51

0

我讀了「標準C++庫」,我找到另一個solution.That是:重新實現該功能的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); 
    } 
} 

它的工作。

但我有點好奇。這個實現不使用傳遞的謂詞op的副本來處理其餘的元素?

我是新來學習STL.I willappreciate您耐心的答案。

非常感謝。