Preferisci gli algoritmi ai loop scritti a mano?

9

Quale dei seguenti è più leggibile? Il ciclo scritto a mano:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

Oppure la chiamata all'algoritmo:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

Mi chiedo se std::for_each valga davvero la pena, dato un esempio così semplice richiede già tanto codice.

Quali sono i tuoi pensieri in merito?

    
posta fredoverflow 15.01.2011 - 11:32
fonte

10 risposte

18

C'è una ragione per cui sono state introdotte lambdas, ed è perché anche il Comitato Standard riconosce che il secondo modulo fa schifo. Usa il primo modulo, finché non ottieni C ++ 0x e il supporto lambda.

    
risposta data 15.01.2011 - 11:34
fonte
8

Utilizza sempre la variante che descrive meglio cosa intendi fare. Questo è

For each element x in vec, do bar.process(x).

Ora esaminiamo gli esempi:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

Anche lì abbiamo un for_each - yippeh . Abbiamo l'intervallo [begin; end) su cui vogliamo operare.

In linea di principio, l'algoritmo era molto più esplicito e quindi preferibile rispetto a qualsiasi implementazione scritta a mano. Ma poi ... Binders? Memfun? Fondamentalmente C ++ interna di come entrare in possesso di una funzione membro? Per il mio compito, Non mi interessa su di loro! Né voglio soffrire di questa verbosa sintassi inquietante.

Ora l'altra possibilità:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

Certo, questo è un modello comune per riconoscere, ma ... creare iteratori, loop, incrementare, dereferenziare. Anche queste sono tutte cose a cui non mi interessa per poter svolgere il mio compito.

Certo, sembra migliore della prima soluzione (almeno, il ciclo body è flessibile e abbastanza esplicito), ma ancora, non è quello fantastico. Useremo questo se non avessimo possibilità migliori, ma forse abbiamo ...

Un modo migliore?

Ora torna a for_each . Non sarebbe bello dire letteralmente for_each e essere flessibili nell'operazione che deve essere fatta anche tu? Fortunatamente, dal momento che C ++ 0x lambda, siamo

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

Ora che abbiamo trovato una soluzione generica e astratta a molte situazioni correlate, vale la pena notare che in questo caso particolare, esiste un # 1 favorite :

foreach(const Foo& x, vec) bar.process(x);

In realtà non può essere molto più chiaro di quello. Per fortuna, C ++ 0x ha una sintassi simile built-in !

    
risposta data 15.01.2011 - 19:45
fonte
8

Perché è così illeggibile?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}
    
risposta data 15.01.2011 - 20:09
fonte
3

in generale, la prima forma è leggibile da praticamente chiunque conosca che cos'è un ciclo for, indipendentemente dallo sfondo di wat che hanno.

anche in generale, il secondo non è affatto leggibile: abbastanza facile capire cosa sta facendo for_each, ma se non hai mai visto std::bind1st(std::mem_fun_ref posso immaginare che sia difficile da capire.

    
risposta data 15.01.2011 - 13:44
fonte
2

In realtà, anche con C ++ 0x, non sono sicuro che for_each otterrà molto amore.

for(Foo& foo: vec) { bar.process(foo); }

È molto più leggibile.

L'unica cosa che non mi piace degli algoritmi (in C ++) è il loro ragionamento sugli iteratori, rende dichiarazioni molto prolisse.

    
risposta data 15.01.2011 - 14:31
fonte
2

Se avessi scritto la barra come un functor, sarebbe molto più semplice:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

Qui il codice è abbastanza leggibile.

    
risposta data 15.01.2011 - 16:24
fonte
1

Preferisco quest'ultimo perché è pulito e ordinato. In realtà è parte di molti altri linguaggi, ma in C ++ fa parte della libreria. Non è davvero importante.

    
risposta data 07.02.2011 - 18:32
fonte
0

Ora questo problema in realtà non è specifico per C ++, direi.

Il fatto è che passare un certo functor in un'altra funzione è non inteso a sostituire i loop .
Si suppone che semplifichi determinati schemi, che diventano molto naturali con la programmazione funzionale, come visitatore e observer , solo per citarne due, che mi vengono in mente immediatamente.
In linguaggi privi di funzioni di primo ordine (probabilmente Java è l'esempio migliore), tali approcci richiedono sempre l'implementazione di una determinata interfaccia, che è abbastanza verbo e ridondante.

Un uso comune che vedo molto in altre lingue potrebbe essere:

someCollection.forEach(someUnaryFunctor);

Il vantaggio di questo è che non devi sapere come viene effettivamente implementato someCollection o cosa fa someUnaryFunctor . Tutto quello che devi sapere è che il suo metodo forEach itererà tutti gli elementi della raccolta, passandoli alla funzione data.

Personalmente, se sei nella posizione, per avere tutte le informazioni sulla struttura dei dati che vuoi ripetere e tutte le informazioni su ciò che vuoi fare in ogni fase di iterazione, allora un approccio funzionale sta complicando le cose, specialmente in una lingua, dove questo è apparentemente piuttosto noioso.

Inoltre, dovresti tenere a mente che l'approccio funzionale è più lento, perché hai un sacco di chiamate, che arrivano a un certo costo, che non hai in un ciclo for.

    
risposta data 15.01.2011 - 20:38
fonte
0

Penso che il problema qui sia che for_each non è in realtà un algoritmo, è solo un modo diverso (e solitamente inferiore) di scrivere un ciclo scritto a mano. da questa prospettiva dovrebbe essere l'algoritmo standard meno utilizzato, e sì probabilmente si potrebbe anche usare un ciclo for. tuttavia il consiglio nel titolo è ancora valido in quanto esistono diversi algoritmi standard adattati a usi più specifici.

Dove c'è un algoritmo che fa più strettamente ciò che vuoi allora sì, dovresti preferire algoritmi su loop scritti a mano. ovvi esempi qui sono l'ordinamento con sort o stable_sort o la ricerca con lower_bound , upper_bound o equal_range , ma la maggior parte di loro ha qualche situazione in cui è preferibile utilizzare su un ciclo codificato a mano.

    
risposta data 07.02.2011 - 15:51
fonte
0

Per questo piccolo frammento di codice, entrambi sono uguali per me leggibili. In generale, trovo i loop manuali incline agli errori e preferisco usare gli algoritmi, ma dipende in realtà da una situazione concreta.

    
risposta data 07.02.2011 - 18:48
fonte

Leggi altre domande sui tag