2010-01-12 13 views
21

Ho appena letto il codice per std :: for_each:Perché la funzione std :: for_each (from, to, function) restituisce?

template<class InputIterator, class Function> 
Function for_each(InputIterator first, InputIterator last, Function f) 
{ 
    for (; first!=last; ++first) f(*first); 
    return f; 
} 

e non riuscivo a vedere alcun buone ragioni per questo modello di funzione per restituire la funzione di ingresso. Qualcuno ha qualche esempio su dove sarebbe utile?

risposta

40

È per consentire all'utente di accumulare lo stato nella propria funzione e quindi restituirlo al proprio codice di chiamata. Ad esempio, la tua funzione (come una classe functor) potrebbe avere un membro int per il conteggio del numero di volte in cui è stato chiamato.

Ecco una pagina con alcuni esempi: http://xenon.arcticus.com/c-morsels-std-for-each-functors-member-variables

+0

Naturalmente, perché non ci ho pensato? Thx :) – larsmoa

+0

Mi è stato detto una volta qui (da Charles Bailey, credo), che questo comportamento non è garantito. Non gli credevo; Pensavo che lo standard, sebbene non chiaro, fosse per lo più sensato in quel contesto. (Perché altrimenti avremmo una funzione?) Ma solo un avvertimento, forse questo è solo un comportamento del compilatore "standard" e non un comportamento standard. – GManNickG

+4

@GMan: la restituzione della funzione è richiesta dallo standard (§25.1.1/2). –

1

è utile se si desidera salvare (e in seguito utilizzare) lo stato del funtore tra le chiamate, ad esempio, contare il numero di elementi in una raccolta o indicare un errore nell'elaborare un elemento impostando alcune variabili interne.

-3

Nessun motivo particolare immagino. Quello che puoi fare è usare la funzione restituita in un'altra chiamata foreach, evitando così di scrivere il nome della funzione due volte e magari commettere un errore lì.

1

Se si passa in un oggetto funzione, alias functor, e ha stato, restituendo l'oggetto funzione si consente di accedere al suo stato dopo aver iterato la sequenza. Supponiamo che tu abbia un oggetto funzione che calcola tre variabili diverse dalla sequenza e le mantiene in variabili membro. Ogni volta che viene chiamato il functor, si aggiornano i contatori. Se for_each non ha restituito l'oggetto, come otterresti il ​​risultato?

Nota ... questo è il motivo per cui è necessario implementare sempre la costruzione della copia e l'assegnazione per gli oggetti funzione con stato.

+3

Non si DEVE ATTUARE il costruttore di copie o l'operatore di assegnazione per un functor, più di quanto fai per qualsiasi altra classe - i valori predefiniti faranno molto probabilmente ciò che è necessario. –

2

Il ripristino della funzione rende in pratica std::for_each in una mediocre imitazione di std::accumulate. Ti consente di accumulare qualcosa nella funzione/funtore, e quindi recuperare quel valore accumulato quando è fatto. Quasi ogni volta che pensi che questa potrebbe essere una cosa utile da fare, dovresti probabilmente considerare l'utilizzo di std::accumulate.

+0

effettivamente std :: accumulate è molto maldestro. – CashCow

7

Può essere che Alex Stepanov aveva il paradigma di programmazione funzionale, ma si troveranno che sia std::accumulate e std::for_each passaggio loro operandi intorno (la funzione e il valore accumulato) per valore, piuttosto che facendo riferimento. Così:

class MyFunctor 
{ 
    Y val; 
    public: 
    MyFunctor() : val() {} 

    void operator()(X const& x) 
    { 
     // do something to modify val based on x 
    } 

    Y getValue() const { return val; } 
}; 

Ora, se si tenta:

MyFunctor f; 
for_each(coll.begin(), coll.end(), f); 
Y y = f.getValue(); 

Non funzionerà perché for_each si occupa di copie di f. Ovviamente si potrebbe avere internamente un'istanza di shared_ptr<Y> che quindi punta alla stessa istanza. Puoi anche fare in modo che val all'interno di MyFunctor sia un riferimento, crearlo all'esterno del loop e passarlo a MyFunctor.

Tuttavia la lingua che si lascia solo fare:

Y y = for_each(coll.begin(), coll.end(), MyFunctor()).getValue(); 

bello e comodo, il tutto in una sola riga.

di fare lo stesso con std::accumulate sarebbe stato fatto in questo modo:

class MyFunctor2 
{ 
public: 
     Y operator()(Y y, X const& x) const 
     { 
     // create a new Y based on the old one and x 
     ... 
     } 
}; 

Y y = std::accumulate(coll.begin(), coll.end(), Y(), MyFunctor2()); 

è possibile utilizzare una funzione (o in C++ 11 a lambda) al posto di un funtore. Nota che qui il functor non ha uno stato e tu passi l'oggetto inizializzato come parametro, che può essere temporaneo.

Ora sappiamo che Y è copiabile. std::accumulate utilizza by value su Y, non una modifica sul posto. Per inciso, quando sul posto modificare realmente è più efficiente, c'è una soluzione senza scrivere un nuovo algoritmo (es accumulate2 che utilizza + = o modifica riferimento) utilizzando una firma funzione di:

Y * func(Y* py, X const &); // function modifies *py in-place then returns py 

quindi chiamando:

Y y; 
std::accumulate(coll.begin(), coll.end(), &y, func); 

"Sappiamo" che il valore restituito sarà & y. Possiamo fare uso di questo se vogliamo accedere a un membro di Y in un posto, ad es.

Y y; 
Z z = std::accumulate(coll.begin(), coll.end(), &y, func)->getZ(); 

Per inciso, una differenza chiave per la copia in for_each e la copia in accumulate è la complessità/numero di copie si farà. Con for_each ci saranno al massimo 2 copie del tuo funtore: uno come parametro nella funzione e uno nel ritorno. Dico "al massimo" perché Return Value Optimization potrebbe ridurre la seconda di queste copie. Con accumulate copia con ogni elemento della raccolta, cioè O(N) piuttosto che con tempo costante. Quindi, se la copia è leggermente costosa, la doppia copia nel funtore non sarà una spesa importante che itera un piccolo numero di volte su raccolte di grandi dimensioni, mentre per accumulare sarebbe (e il suggerimento sarebbe il trucco puntatore).

Problemi correlati