Existem vantagens do std::for_each
over for
loop? Para mim, std::for_each
apenas parece dificultar a legibilidade do código. Por que alguns padrões de codificação recomendam seu uso?
Existem vantagens do std::for_each
over for
loop? Para mim, std::for_each
apenas parece dificultar a legibilidade do código. Por que alguns padrões de codificação recomendam seu uso?
Respostas:
O interessante do C ++ 11 (anteriormente chamado C ++ 0x) é que esse debate cansativo será resolvido.
Quero dizer, ninguém em sã consciência, que deseja iterar uma coleção inteira, ainda usará isso
for(auto it = collection.begin(); it != collection.end() ; ++it)
{
foo(*it);
}
Ou isto
for_each(collection.begin(), collection.end(), [](Element& e)
{
foo(e);
});
quando a sintaxe do loop baseado em intervalofor
estiver disponível:
for(Element& e : collection)
{
foo(e);
}
Esse tipo de sintaxe está disponível em Java e C # há algum tempo e, na verdade, existem muito mais foreach
loops que for
loops clássicos em todos os códigos Java ou C # recentes que vi.
Element & e
que auto & e
(ou auto const &e
) pareça melhor. Eu usaria Element const e
(sem referência) quando quiser uma conversão implícita, digamos quando a fonte for uma coleção de tipos diferentes e quero que eles se convertam Element
.
Aqui estão alguns motivos:
Parece dificultar a legibilidade apenas porque você não está acostumado a isso e / ou não está usando as ferramentas certas para torná-lo realmente fácil. (consulte boost :: range e boost :: bind / boost :: lambda para auxiliares. Muitos deles vão para o C ++ 0x e tornam o for_each e as funções relacionadas mais úteis.)
Ele permite que você escreva um algoritmo sobre for_each que funcione com qualquer iterador.
Reduz a chance de erros estúpidos de digitação.
Ele também abre a sua mente para o resto dos STL-algoritmos, como find_if
, sort
, replace
, etc e estes não vão olhar mais tão estranho. Isso pode ser uma grande vitória.
Atualização 1:
Mais importante, ele ajuda você a ir além dos for_each
x for-loops, como é tudo o que existe, e olhar para os outros STL-alogs, como find / sort / partition / copy_replace_if, execução paralela .. ou o que for.
Muito processamento pode ser escrito de forma muito concisa usando "o resto" dos irmãos de for_each, mas se tudo o que você faz é escrever um loop for com várias lógicas internas, nunca aprenderá como usá-las e acabam inventando a roda repetidamente.
E (em breve disponível para cada estilo de intervalo):
for_each(monsters, boost::mem_fn(&Monster::think));
Ou com lambdas C ++ x11:
for_each(monsters, [](Monster& m) { m.think(); });
é IMO mais legível do que:
for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
i->think();
}
Também isso (ou com lambdas, veja outros):
for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));
É mais conciso do que:
for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
my_monkey->eat(*i);
}
Especialmente se você tiver várias funções para chamar em ordem ... mas talvez seja apenas eu. ;)
Atualização 2 : eu escrevi meus próprios invólucros de uma linha de stl-algos que funcionam com intervalos em vez de pares de iteradores. O boost :: range_ex, uma vez lançado, incluirá isso e talvez também esteja lá no C ++ 0x?
outer_class::inner_class::iterator
ou são argumentos de modelo: typename std::vector<T>::iterator
... o construtor for em si pode executar em si mesmo um construtor de muitas linhas
for_each
no segundo exemplo está incorreto (deve serfor_each( bananas.begin(), bananas.end(),...
for_each
é mais genérico. Você pode usá-lo para iterar sobre qualquer tipo de contêiner (passando os iteradores de início / fim). Você pode trocar os contêineres potencialmente por baixo de uma função usada for_each
sem precisar atualizar o código de iteração. Você precisa considerar que existem outros contêineres no mundo além de std::vector
matrizes C simples para ver as vantagens de for_each
.
A principal desvantagem for_each
é que é preciso um functor, portanto a sintaxe é desajeitada. Isso foi corrigido no C ++ 11 (anteriormente C ++ 0x) com a introdução de lambdas:
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
Isso não parecerá estranho para você em três anos.
for ( int v : int_vector ) {
(mesmo que pode ser simulado hoje com BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
. Quero dizer, por que alguém é forçado a escrever contêiner duas vezes?
container.each { ... }
sem mencionar os iteradores de início e fim. Acho um pouco redundante precisar especificar o iterador final o tempo todo.
Pessoalmente, sempre que eu precisar sair do meu caminho para usar std::for_each
(escreva functors para fins especiais / boost::lambda
s complicados ), acho que o BOOST_FOREACH
C ++ 0x é baseado em intervalo para maior clareza:
BOOST_FOREACH(Monster* m, monsters) {
if (m->has_plan())
m->act();
}
vs
std::for_each(monsters.begin(), monsters.end(),
if_then(bind(&Monster::has_plan, _1),
bind(&Monster::act, _1)));
é muito subjetivo, alguns dirão que o uso for_each
tornará o código mais legível, pois permite tratar diferentes coleções com as mesmas convenções.
for_each
itslef é implementado como um loop
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}
então cabe a você escolher o que é certo para você.
Como muitas das funções do algoritmo, uma reação inicial é pensar que é mais ilegível usar foreach do que um loop. Tem sido um tópico de muitas guerras de chamas.
Depois de se acostumar com o idioma, você pode achar útil. Uma vantagem óbvia é que ele força o codificador a separar o conteúdo interno do loop da funcionalidade de iteração real. (OK, acho que é uma vantagem. Outros dizem que você está apenas cortando o código sem nenhum benefício real).
Uma outra vantagem é que, quando vejo foreach, sei que todos os itens serão processados ou uma exceção será lançada.
Um loop for permite várias opções para finalizar o loop. Você pode deixar o loop executar seu curso completo ou usar a palavra-chave break para sair explicitamente do loop ou usar a palavra-chave return para sair de toda a função no meio do loop. Por outro lado, o foreach não permite essas opções, e isso a torna mais legível. Você pode apenas olhar o nome da função e conhecer a natureza completa da iteração.
Aqui está um exemplo de um loop for confuso :
for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
/////////////////////////////////////////////////////////////////////
// Imagine a page of code here by programmers who don't refactor
///////////////////////////////////////////////////////////////////////
if(widget->Cost < calculatedAmountSofar)
{
break;
}
////////////////////////////////////////////////////////////////////////
// And then some more code added by a stressed out juniour developer
// *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
/////////////////////////////////////////////////////////////////////////
for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
{
if(ip->IsBroken())
{
return false;
}
}
}
std::for_each()
o padrão antigo (o da época desta postagem), é necessário usar um functor nomeado, o que incentiva a legibilidade, como você diz, e proíbe interromper prematuramente o ciclo. Mas o for
loop equivalente não tem mais que uma chamada de função, e isso também proíbe a interrupção prematura. Mas, além disso, acho que você fez um excelente argumento ao dizer que std::for_each()
reforça o alcance de toda a gama.
Você está mais correto: na maioria das vezes, std::for_each
é uma perda líquida. Eu iria tão longe para comparar for_each
a goto
. goto
fornece o controle de fluxo mais versátil possível - você pode usá-lo para implementar praticamente qualquer outra estrutura de controle que você possa imaginar. Essa versatilidade, no entanto, significa que ver um goto
isoladamente não diz praticamente nada sobre o que se pretende fazer nessa situação. Como resultado, quase ninguém em sã consciência usa, goto
exceto como último recurso.
Entre os algoritmos padrão, for_each
é praticamente o mesmo - ele pode ser usado para implementar praticamente qualquer coisa, o que significa que ver não for_each
diz praticamente nada sobre o que está sendo usado nessa situação. Infelizmente, a atitude das pessoas em relação ao local for_each
é onde elas goto
estavam (digamos) em 1970, aproximadamente - algumas pessoas perceberam que ela deveria ser usada apenas como último recurso, mas muitas ainda a consideram o algoritmo primário, e raramente, se alguma vez, usar outro. Na grande maioria das vezes, mesmo um rápido olhar revelaria que uma das alternativas era drasticamente superior.
Apenas por exemplo, tenho certeza de que perdi o controle de quantas vezes vi pessoas escrevendo código para imprimir o conteúdo de uma coleção usando for_each
. Com base nas postagens que eu já vi, esse pode ser o uso mais comum for_each
. Eles acabam com algo como:
class XXX {
// ...
public:
std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};
E seu posto está perguntando sobre qual combinação de bind1st
, mem_fun
, etc. Eles precisam fazer algo como:
std::vector<XXX> coll;
std::for_each(coll.begin(), coll.end(), XXX::print);
trabalho e imprima os elementos de coll
. Se realmente funcionou exatamente como eu escrevi lá, seria medíocre, mas não funciona - e quando você o faz funcionar, é difícil encontrar esses poucos bits de código relacionados ao que é acontecendo entre as peças que a mantêm unida.
Felizmente, existe uma maneira muito melhor. Adicione uma sobrecarga normal do inserdor de fluxo para XXX:
std::ostream &operator<<(std::ostream *os, XXX const &x) {
return x.print(os);
}
e use std::copy
:
std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));
Isso funciona - e praticamente não exige trabalho algum para descobrir que ele imprime o conteúdo de coll
para std::cout
.
boost::mem_fn(&XXX::print)
melhor queXXX::print
std::cout
como argumento para que funcione).
A vantagem de escrever funcional para ser mais legível pode não aparecer quando for(...)
e for_each(...
).
Se você utiliza todos os algoritmos em functional.h, em vez de usar loops for, o código fica muito mais legível;
iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);
é muito mais legível do que;
Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (*it > *longest_tree) {
longest_tree = it;
}
}
Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
if (it->type() == LEAF_TREE) {
leaf_tree = it;
break;
}
}
for (Forest::const_iterator it = forest.begin(), jt = firewood.begin();
it != forest.end();
it++, jt++) {
*jt = boost::transformtowood(*it);
}
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
std::makeplywood(*it);
}
E é isso que eu acho tão legal: generalize os for-loops para as funções de uma linha =)
Fácil: for_each
é útil quando você já possui uma função para lidar com todos os itens da matriz, para que não precise escrever uma lambda. Certamente, isso
for_each(a.begin(), a.end(), a_item_handler);
é melhor que
for(auto& item: a) {
a_item_handler(a);
}
Além disso, o for
loop à distância itera apenas contêineres inteiros do início ao fim, enquanto for_each
é mais flexível.
O for_each
loop visa ocultar os iteradores (detalhes de como um loop é implementado) do código do usuário e definir semânticas claras na operação: cada elemento será iterado exatamente uma vez.
O problema com a legibilidade no padrão atual é que ele requer um functor como o último argumento em vez de um bloco de código; portanto, em muitos casos, você deve escrever um tipo específico de functor para ele. Isso se transforma em código menos legível, pois os objetos functor não podem ser definidos no local (classes locais definidas em uma função não podem ser usadas como argumentos de modelo) e a implementação do loop deve ser afastada do loop real.
struct myfunctor {
void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(), myfunctor() );
// more code
}
Observe que, se você deseja executar uma operação específica em cada objeto, pode usar std::mem_fn
, ou boost::bind
( std::bind
no próximo padrão) ou boost::lambda
(lambdas no próximo padrão) para simplificá-lo:
void function( int value );
void apply( std::vector<X> const & v ) {
// code
std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
// code
}
O que não é menos legível e mais compacto do que a versão manual, se você tiver uma função / método para chamar. A implementação pode fornecer outras implementações do for_each
loop (pense em processamento paralelo).
O próximo padrão trata de algumas das deficiências de maneiras diferentes, permitindo classes definidas localmente como argumentos para modelos:
void apply( std::vector<int> const & v ) {
// code
struct myfunctor {
void operator()( int ) { code }
};
std::for_each( v.begin(), v.end(), myfunctor() );
// code
}
Melhorando a localidade do código: quando você navega, vê o que está fazendo ali. Por uma questão de fato, você nem precisa usar a sintaxe da classe para definir o functor, mas use um lambda ali:
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(),
[]( int ) { // code } );
// code
}
Mesmo que, no caso de, for_each
exista uma construção específica que a torne mais natural:
void apply( std::vector<int> const & v ) {
// code
for ( int i : v ) {
// code
}
// code
}
Costumo misturar a for_each
construção com loops enrolados à mão. Quando apenas uma chamada para uma função ou método existente é o que eu preciso ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )
), busco a for_each
construção que retira do código muitas coisas do iterador da placa da caldeira. Quando preciso de algo mais complexo e não consigo implementar um functor apenas algumas linhas acima do uso real, rolo meu próprio loop (mantém a operação no lugar). Em seções não críticas do código, posso usar o BOOST_FOREACH (um colega de trabalho me envolveu)
Além da legibilidade e desempenho, um aspecto geralmente esquecido é a consistência. Existem várias maneiras de implementar um loop for (ou while) nos iteradores, a partir de:
for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
do_something(*iter);
}
para:
C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
do_something(*iter);
++iter;
}
com muitos exemplos entre os diferentes níveis de eficiência e potencial de erros.
O uso de for_each, no entanto, reforça a consistência abstraindo o loop:
for_each(c.begin(), c.end(), do_something);
A única coisa com a qual você precisa se preocupar agora é: você implementa o corpo do loop como função, um functor ou um lambda usando os recursos Boost ou C ++ 0x? Pessoalmente, prefiro me preocupar com isso do que como implementar ou ler um loop for / while aleatório.
Eu não gostava std::for_each
e pensava que, sem o lambda, isso era totalmente errado. No entanto, mudei de idéia há algum tempo, e agora eu realmente amo isso. E acho que melhora a legibilidade e facilita o teste do seu código de forma TDD.
O std::for_each
algoritmo pode ser lido como fazer algo com todos os elementos no intervalo , o que pode melhorar a legibilidade. Digamos que a ação que você deseja executar tenha 20 linhas e a função em que a ação é realizada também tenha cerca de 20 linhas. Isso tornaria uma função de 40 linhas com um loop for convencional e apenas cerca de 20 com std::for_each
, portanto, provavelmente mais fácil de compreender.
É std::for_each
mais provável que os funcores sejam mais genéricos e, portanto, reutilizáveis, por exemplo:
struct DeleteElement
{
template <typename T>
void operator()(const T *ptr)
{
delete ptr;
}
};
E no código, você teria apenas uma linha como std::for_each(v.begin(), v.end(), DeleteElement())
IMO ligeiramente melhor que um loop explícito.
Todos esses functores são normalmente mais fáceis de serem submetidos a testes de unidade do que um loop for explícito no meio de uma função longa, e isso por si só já é uma grande vitória para mim.
std::for_each
também é geralmente mais confiável, pois é menos provável que você cometa um erro de alcance.
E, por fim, o compilador pode produzir um código um pouco melhor para o std::for_each
que para certos tipos de loop for criados manualmente, pois (for_each) sempre parece o mesmo para o compilador, e os escritores do compilador podem colocar todo o seu conhecimento para torná-lo tão bom quanto possível. pode.
Mesmo se aplica a outros algoritmos de DST como find_if
, transform
etc.
for
é para loop que pode iterar cada elemento ou cada terceiro etc. for_each
é para iterar apenas cada elemento. Está claro pelo nome. Portanto, fica mais claro o que você pretende fazer no seu código.
++
. Incomum talvez, mas o loop for também está fazendo o mesmo.
transform
para não confundir alguém.
Se você usa frequentemente outros algoritmos do STL, há várias vantagens em for_each
:
Ao contrário de um loop for tradicional, for_each
obriga a escrever um código que funcione para qualquer iterador de entrada. Restringir dessa maneira pode ser uma coisa boa porque:
for_each
.for_each
Às vezes, o uso torna mais óbvio que você pode usar uma função STL mais específica para fazer a mesma coisa. (Como no exemplo de Jerry Coffin; não é necessariamente o caso que for_each
é a melhor opção, mas um loop for não é a única alternativa.)
Com o C ++ 11 e dois modelos simples, você pode escrever
for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}
como um substituto para for_each
ou um loop. Por que a escolha se resume a brevidade e segurança, não há chance de erro em uma expressão que não existe.
Para mim, for_each
sempre foi melhor pelo mesmo motivo quando o corpo do loop já é um functor e aproveitarei qualquer vantagem que puder obter.
Você ainda usa as três expressões for
, mas agora, quando você vê uma que sabe que há algo para entender, não é uma clichê. Eu odeio clichê. Eu me ressinto de sua existência. Não é um código real, não há nada a aprender lendo-o, é apenas mais uma coisa que precisa ser verificada. O esforço mental pode ser medido pelo quão fácil é ficar enferrujado ao checá-lo.
Os modelos são
template<typename iter>
struct range_ {
iter begin() {return __beg;} iter end(){return __end;}
range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
iter __beg, __end;
};
template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
{ return range_<iter>(begin,end); }
Principalmente, você terá que percorrer toda a coleção . Portanto, sugiro que você escreva sua própria variante for_each (), usando apenas 2 parâmetros. Isso permitirá que você reescreva o exemplo de Terry Mahaffey como:
for_each(container, [](int& i) {
i += 10;
});
Eu acho que isso é realmente mais legível do que um loop for. No entanto, isso requer as extensões do compilador C ++ 0x.
Acho for_each ruim para facilitar a leitura. O conceito é bom, mas o c ++ torna muito difícil escrever legível, pelo menos para mim. As expressões c ++ 0x lamda ajudarão. Eu realmente gosto da idéia de lamdas. No entanto, à primeira vista, acho que a sintaxe é muito feia e não tenho 100% de certeza de que vou me acostumar. Talvez em cinco anos eu me acostumei e não pense duas vezes, mas talvez não. O tempo vai dizer :)
Eu prefiro usar
vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
// Do stuff
}
Acho que um loop for explícito mais claro para ler e explicitamente, usando variáveis nomeadas para os iteradores de início e fim, reduz a desordem no loop for.
É claro que os casos variam, é exatamente o que eu costumo achar melhor.
Você pode fazer com que o iterador seja uma chamada para uma função que é executada em cada iteração pelo loop.
Veja aqui: http://www.cplusplus.com/reference/algorithm/for_each/
for_each
que, nesse caso, não responde à pergunta sobre suas vantagens.
For loop pode quebrar; Eu não quero ser um papagaio para Herb Sutter, então aqui está o link para sua apresentação: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Não deixe de ler também os comentários :)
for_each
nos permitem implementar o padrão de junção de garfo . Fora isso, ele suporta interface fluente .
Podemos adicionar implementação gpu::for_each
para usar cuda / gpu para computação paralela heterogênea chamando a tarefa lambda em vários trabalhadores.
gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.
E gpu::for_each
pode esperar que os trabalhadores trabalhem em todas as tarefas lambda para concluir antes de executar as próximas instruções.
Isso nos permite escrever código legível por humanos de maneira concisa.
accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
std::for_each
quando usado comboost.lambda
ouboost.bind
pode melhorar a legibilidade