2012-04-22 13 views
7

Nel seguente programma C++ STL, definisco un functh Nth e restituisce true se viene revocato nell'ennesima volta. E lo trasformo nell'algoritmo generico remove_if, ottengo qualcosa strano.Un programma STL C++ che utilizza il predicato come predicato

Il codice:

#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; 
} 

lo eseguo in Microsoft Visual Studio 2008 e ottengo il risultato: enter image description here Cancella gli elementi 3 e 6 che non è che voglio. Ho pensato che solo 3 sarebbero stati cancellati. Qualcuno potrebbe interpretare per me? Grazie mille.

risposta

11

From The C++ Standard Library: Un tutorial e una di riferimento da Nicolai M. Josuttis

Questo accade perché il solito attuazione delle copie algoritmo predicato internamente durante l'algoritmo:

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); 
     } 
    } 

Gli usi algoritmo find_if() per trovare il primo elemento che dovrebbe essere rimosso. Tuttavia, utilizza quindi una copia dell'opzione predicato passato per elaborare gli eventuali elementi rimanenti. Qui, Nth nel suo stato originale viene nuovamente utilizzato e rimuove anche il terzo elemento degli elementi rimanenti, che è in effetti il ​​sesto elemento.

Questo comportamento non è un bug. Lo standard non specifica la frequenza con cui un predicato può essere copiato internamente da un algoritmo. Quindi, per ottenere il comportamento garantito della libreria standard C++, non si dovrebbe passare un oggetto funzione per il quale il comportamento dipende da quanto spesso viene copiato o chiamato. Quindi, se si chiama un predicato unario per due argomenti ed entrambi gli argomenti sono uguali, allora il predicato dovrebbe sempre produrre lo stesso risultato. Cioè, un predicato non dovrebbe cambiare il suo stato a causa di una chiamata, e una copia di un predicato dovrebbe avere lo stesso stato dell'originale. Per garantire che non sia possibile modificare lo stato di un predicato a causa di una chiamata di funzione, è necessario dichiarare operator() come funzione membro costante.

+1

Per essere più precisi, ciò che l'OP desidera raggiungere è ancora possibile. Lo stato dovrebbe essere esternalizzato e passato al predicato sotto forma di riferimento mutabile. Quindi tutte le copie di un determinato predicato condivideranno lo stesso stato mutabile dell'originale. –

5

Non utilizzare std::remove_if su un std::list. Invece, utilizzare la funzione di membro della lista:

col.remove_if(Nth(3)); 

L'algoritmo generico riorganizza i valori di elementi in modo che è possibile cancellare in modo sicuro dalla fine, ma per una lista, l'algoritmo di membro rimuove le indesiderate nodi direttamente senza toccare qualsiasi altro elemento.

Aggiornamento . Come è stato sottolineato, questo non è in realtà garantito per risolvere il tuo problema, dal momento che il tuo predicato non può avere uno stato di valore interno. Prova invece:

Questo nuovo predicato è copiabile e funge da riferimento attorno alla variabile del contatore (esterna).

+0

Ciò non garantisce solo l'uso di una singola copia del predicato, più di quanto non lo sia 'std :: remove_if'. Se sembra risolvere questo problema, allora è solo per caso. –

+0

@ MikeSeymour: Sì, hai ragione. Aggiungerò una nota. –

0

ho letto il "La libreria standard C++", e trovo un altro solution.That è: re-implementare la funzione 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); 
    } 
} 

funziona.

Ma io sono un po 'curioso. Fa questo strumento non utilizzare una copia del predicato passato op per elaborare gli elementi rimanenti ???

Sono nuovo per imparare STL.I willappreciate per le risposte dei pazienti.

Grazie mille.