Prefere algoritmos a loops escritos à mão?

10

Qual das seguintes opções você acha mais legível? O loop escrito à mão:

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

Ou a chamada do algoritmo:

#include <algorithm>
#include <functional>

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

Gostaria de saber se std::for_eachrealmente vale a pena, dado um exemplo tão simples já requer tanto código.

Quais são os seus pensamentos em relação a este assunto?

fredoverflow
fonte
2
Para C ++, a resposta é bastante óbvia. Mas em outros idiomas, ambos são quase iguais (por exemplo, Python:, map(bar.process, vec)embora o mapa para efeitos colaterais seja desencorajado e as compreensões de lista / expressões geradoras sejam recomendadas sobre o mapa).
5
E também há BOOST_FOREACH...
Benjamin Bannier
Então, o item no STL efetivo não o convenceu? stackoverflow.com/questions/135129/…
Job

Respostas:

18

Há uma razão pela qual as lambdas foram introduzidas, e é porque até o Comitê Padrão reconhece que a segunda forma é péssima. Use o primeiro formulário, até obter suporte para C ++ 0x e lambda.

DeadMG
fonte
2
O mesmo raciocínio pode ser usado para justificar a segunda forma: o Comitê de Padrões reconheceu que era tão superior que eles introduziram lambdas para torná-la ainda melhor. :-p (+1 no entanto)
Konrad Rudolph
Eu não acho que o segundo é tão ruim assim. Mas pode ser melhor. Por exemplo, você deliberadamente tornou o objeto Bar mais difícil de usar, não adicionando operator (). Ao usar isso, ele simplifica bastante o ponto de chamada no loop e torna o código arrumado.
Martin York
Nota: o suporte ao lambda foi adicionado devido a duas queixas principais. 1) O padrão padrão necessário para passar parâmetros para os functores. 2) Não tendo o código no ponto de chamada. Seu código não satisfaz nenhum deles, pois você está apenas chamando um método. Portanto, 1) nenhum código extra para passar os parâmetros 2) O código já não está no ponto de chamada, portanto, o lambda não melhorará a manutenção do código. Então, sim, lambda é uma ótima adição. Este não é um bom argumento para eles.
Martin York
9

Sempre use a variante que descreve melhor o que você pretende fazer. Isso é

Para cada elemento xem vec, faça bar.process(x).

Agora, vamos examinar os exemplos:

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

Temos um for_eachlá também - yippeh . Temos o [begin; end)alcance em que queremos operar.

Em princípio, o algoritmo era muito mais explícito e, portanto, preferível a qualquer implementação escrita à mão. Mas então ... Binders? Memfun? Basicamente C ++ interna de como se apossar de uma função de membro? Para a minha tarefa, eu não me importo com eles! Também não quero sofrer com essa sintaxe detalhada e assustadora.

Agora a outra possibilidade:

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

É verdade que esse é um padrão comum para reconhecer, mas ... criar iteradores, repetir, incrementar, desreferenciar. Essas também são todas as coisas de que não me importo para realizar minha tarefa.

É certo que parece melhor do que a primeira solução (pelo menos, o corpo do loop é flexível e bastante explícito), mas ainda assim, não é realmente tão bom assim. Usaremos este se não tivermos uma possibilidade melhor, mas talvez tenhamos ...

Uma maneira melhor?

Agora de volta para for_each. Não seria ótimo dizer literalmente for_each e ser flexível na operação a ser realizada também? Felizmente, desde C ++ 0x lambdas, estamos

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

Agora que encontramos uma solução abstrata e genérica para muitas situações relacionadas, vale a pena notar que, neste caso em particular, existe um favorito nº 1 absoluto :

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

Realmente não pode ficar muito mais claro que isso. Felizmente, C ++ 0x get de uma sintaxe semelhante built-in !

Dario
fonte
2
Disse "sintaxe semelhante" sendo for (const Foo& x : vec) bar.process(x);, ou usando, const auto&se quiser.
Jon Purdy
const auto&é possível? Não sabia disso - ótimas informações!
Dario
8

Porque isso é tão ilegível?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}
Martin Beckett
fonte
4
Desculpe, mas por alguma razão diz "censurado" onde acredito que seu código deveria estar.
Mateen Ulhaq
6
Seu código fornece um aviso ao compilador, porque comparar um intcom um size_té perigoso. Além disso, a indexação não funciona com todos os contêineres, por exemplo std::set.
Fredoverflow
2
Esse código funcionará apenas para contêineres com um operador de indexação, dos quais existem poucos. Ele amarra o algoritmo de forma desinteressada - e se um mapa <> fosse melhor? O loop for original e for_each não serão afetados.
JBWilkinson
2
E quantas vezes você cria código para um vetor e decide trocá-lo por um mapa? Como se projetar para isso fosse a prioridade das línguas.
Martin Beckett
2
A iteração sobre coleções por índice é desencorajada porque algumas coleções têm desempenho fraco na indexação, enquanto todas as coleções se comportam bem ao usar um iterador.
slaphappy
3

em geral, a primeira forma é legível para praticamente qualquer pessoa que saiba o que é um loop for, não importa o que eles tenham.

também em geral, o segundo não é tão legível: é fácil entender o que for_each está fazendo, mas se você nunca viu std::bind1st(std::mem_fun_ref, posso imaginar que é difícil entender.

stijn
fonte
2

Na verdade, mesmo com C ++ 0x, não tenho certeza se for_eachvai receber muito amor.

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

É muito mais legível.

A única coisa que eu não gosto nos algoritmos (em C ++) é que eles raciocinam em iteradores, produzem declarações muito detalhadas.

Matthieu M.
fonte
2

Se você tivesse escrito a barra como um functor, seria muito mais simples:

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


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

Aqui o código é bastante legível.

Martin York
fonte
2
É bem legível, além do fato de que você acabou de escrever um functor.
Winston Ewert
Veja aqui por que eu acho que esse código é pior.
SBI
1

Eu prefiro o último porque é arrumado e limpo. Na verdade, faz parte de muitas outras linguagens, mas em C ++ faz parte da biblioteca. Não é realmente importante.

sarat
fonte
0

Agora, esse problema não é específico para C ++, eu diria.

A questão é que passar algum functor para outra função não visa substituir os loops .
Supõe-se que simplifique certos padrões, que se tornam muito naturais com a programação funcional, como visitante e observador , apenas para citar dois, que vêm à minha mente imediatamente.
Em linguagens que não possuem funções de primeira ordem (Java provavelmente é o melhor exemplo), essas abordagens sempre exigem a implementação de uma determinada interface, que é bastante verbal e redundante.

Um uso comum que vejo muito em outros idiomas seria:

someCollection.forEach(someUnaryFunctor);

A vantagem disso é que você não precisa saber como someCollectioné realmente implementado ou o que someUnaryFunctorfaz. Tudo que você precisa saber é que seu forEachmétodo irá iterar todos os elementos da coleção, passando-os para a função especificada.

Pessoalmente, se você estiver na posição de ter todas as informações sobre a estrutura de dados que deseja iterar e todas as informações sobre o que você deseja fazer em cada etapa da iteração, uma abordagem funcional está complicando demais as coisas, especialmente em um idioma, onde isso é aparentemente bastante entediante.

Além disso, você deve ter em mente que a abordagem funcional é mais lenta, porque você tem muitas chamadas, que têm um certo custo, que você não tem em um loop for.

back2dos
fonte
11
"Além disso, você deve ter em mente que a abordagem funcional é mais lenta, porque você tem muitas chamadas, que têm um certo custo, que você não tem em um loop for". ? Esta declaração não é suportada. A abordagem funcional não é necessariamente mais lenta, especialmente porque pode haver otimizações ocultas. E mesmo que você entenda completamente seu loop, ele provavelmente parecerá mais limpo em sua forma mais abstrata.
Andres F.
@ André F: Então isso parece mais limpo? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));E é mais lento no tempo de execução ou no tempo de compilação (se você tiver sorte). Não vejo por que substituir estruturas de controle de fluxo por chamadas de função melhora o código. Sobre qualquer alternativa apresentada aqui é mais legível.
Back2dos
0

Acho que o problema aqui é que for_eachnão é realmente um algoritmo, é apenas uma maneira diferente (e geralmente inferior) de escrever um loop escrito à mão. dessa perspectiva, ele deve ser o algoritmo padrão menos usado e, sim, você provavelmente também pode usar um loop for. no entanto, o conselho no título ainda permanece, pois existem vários algoritmos padrão personalizados para usos mais específicos.

Onde houver um algoritmo que faça com mais precisão o que você deseja, sim, você deve preferir algoritmos a loops escritos à mão. exemplos óbvios aqui estão classificando com sortou stable_sortou pesquisar com lower_bound, upper_boundou equal_range, mas a maioria deles tem alguma situação em que se encontram preferível usar mais de um loop codificado mão.

jk.
fonte
0

Para este pequeno trecho de código, ambos são igualmente legíveis para mim. Em geral, acho que os loops manuais são propensos a erros e prefiro usar algoritmos, mas isso realmente depende de uma situação concreta.

Nemanja Trifunovic
fonte