É Iterator dentro inicialização loop for considerada um estilo ruim, e por quê?
-
06-07-2019 - |
Pergunta
Normalmente você vai encontrar o código STL como esta:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Mas nós realmente temos a recomendação para escrevê-lo como este:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
Se você está preocupado com o escopo, adicione anexando chaves:
{
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin();
SomeClass::SomeContainer::iterator IterEnd = m_SomeMemberContainerVar.end();
for (; Iter != IterEnd; ++Iter)
{
}
}
Esta é suposto dar um ganho de velocidade e eficiência, especialmente se você estiver consolas de programação, porque a função .end () não é chamado em cada iteração do loop. Eu só tomar a melhoria de desempenho para concedido, soa razoável, mas eu não sei o quanto e certamente depende do tipo de recipiente e implementação STL real em uso. Mas ter usado este estilo para um par de meses agora eu realmente prefiro-o sobre o primeiro de qualquer maneira.
A razão de ser a legibilidade: o para a linha é limpo e arrumado. Com qualificadores e variáveis ??de membro em código de produção real, é muito fácil ter realmente longa para as linhas se você usar o estilo no primeiro exemplo. É por isso que intencionalmente o fizeram para ter uma barra de rolagem horizontal neste exemplo, só assim você vê o que eu estou falando. ;)
Por outro lado, de repente você introduzir as variáveis ??Iter ao âmbito externo do loop for. Mas então, pelo menos no ambiente de trabalho i no, o Iter teria sido acessíveis no âmbito externo, mesmo no primeiro exemplo.
Qual é a sua opinião sobre isso? Há algum do Pro para o primeiro estilo diferente possivelmente limitando o escopo do Iter?
Solução
Se você quebrar seu código em linhas corretamente, o formulário em linha seria igualmente legível. Além disso, você deve sempre fazer o iterEnd = container.end()
como uma otimização:
for (SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
Iter != IterEnd;
++Iter)
{
}
Atualização:. Fixou o código pelo conselho de paercebal
Outras dicas
Outra alternativa é usar uma macro foreach, por exemplo boost foreach :
BOOST_FOREACH( ContainedType item, m_SomeMemberContainerVar )
{
mangle( item );
}
Eu sei macros são desencorajados em c ++ moderno, mas até que a palavra-chave auto é amplamente disponível esta é a melhor maneira que eu encontrei para obter algo que é conciso e legível, e ainda completamente typesafe e rápido. Você pode implementar sua macro usando qualquer estilo de inicialização você recebe um melhor desempenho.
Há também uma nota na página vinculada sobre redefinindo BOOST_FOREACH como foreach para evitar o irritante todos os tampões.
A primeira forma (para dentro do laço) é melhor se a iteração não é necessária depois de o loop. Ela limita o seu alcance para o loop for.
Eu duvido seriamente que haja qualquer ganho de eficiência de qualquer maneira. Ele também pode ser feita mais legível com um typedef.
typedef SomeClass::SomeContainer::iterator MyIter;
for (MyIter Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Eu recomendaria nomes mais curtos; -)
Tendo visto isso em g ++ em -O2 otimização (apenas para ser mais específico)
Não há nenhuma diferença no código gerado para std :: vector, std :: lista e std :: map (e amigos). Há uma pequena sobrecarga com std :: deque.
Assim, em geral, do ponto de vista de desempenho que faz pouca diferença.
Não, não é uma má idéia para obter um porão em iter.end()
antes do laço começar. Se o seu ciclo muda o recipiente, em seguida, o fim iterador pode ser invalidada. Além disso, o método end()
é garantida para ser O (1).
A optimização prematura é a raiz de todo o mal.
Além disso, o compilador pode ser mais inteligente do que você pensa.
Eu não tenho um particularmente forte forma de parecer um ou o outro, embora iterador vida me inclinar-se para a versão para-escopo.
No entanto, a leitura pode ser um problema; que pode ser ajudado usando um typedef de modo que o tipo de iterador é um pouco mais administrável:
typedef SomeClass::SomeContainer::iterator sc_iter_t;
for (sc_iter_t Iter = m_SomeMemberContainerVar.begin(); Iter != m_SomeMemberContainerVar.end(); ++Iter)
{
}
Não é um enorme melhoria, mas um pouco.
Eu não tenho nenhuma experiência com o console, mas na maioria dos compiliers modernas C ++ ou outra opção acaba sendo equivilent exceto para a questão do escopo. O compilier visual studio vai quase sempre, mesmo em código de depuração colocar a comparação condição em uma variável temporária implícita (geralmente um registo). Assim, enquanto logicamente parece que o end () chamada é feita através de cada iteração, o código compilado otimizado realmente só faz a chamada de uma vez a comparação é a única coisa que é feito cada vez subsiquent através do laço.
Este não pode ser o caso em consoles, mas você poderia unassemble o loop para verificar se a otimização está ocorrendo. Se for, então você pode você o que estilo você prefere ou é padrão na sua organização.
Pode fazer para o código desarticulada, mas também gosto de puxá-lo para fora para uma função separada, e passar os dois iteradores para ele.
doStuff(coll.begin(), coll.end())
e ter ..
template<typename InIt>
void doStuff(InIt first, InIt last)
{
for (InIt curr = first; curr!= last; ++curr)
{
// Do stuff
}
}
Encontros gostar:
- Nunca tenho que mencionar o tipo iterator feio (ou pensar sobre se é const ou não-const)
- Se não houver ganho de não chamar end () em cada iteração, eu estou recebendo-lo
As coisas não como:
- rompe-se o código
- aéreo de chamada de função adicional.
Mas um dia, teremos lambdas!
Eu não acho que é mau estilo em tudo. Basta usar typedefs para evitar a verbosidade STL e longas filas.
typedef set<Apple> AppleSet;
typedef AppleSet::iterator AppleIter;
AppleSet apples;
for (AppleIter it = apples.begin (); it != apples.end (); ++it)
{
...
}
Spartan Programação é uma maneira de mitigar as suas preocupações de estilo.
Você pode jogar chaves em todo o inicialização e loop se você está preocupado com escopo. Muitas vezes o que eu vou fazer é iterators declarar, no início da função e reutilizá-los ao longo do programa.
Eu concordo com Ferruccio. O primeiro estilo pode ser preferido por alguns, a fim de puxar o end () chamar para fora do loop.
Eu também poderia acrescentar que C ++ 0x vai realmente fazer as duas versões muito mais limpo:
for (auto iter = container.begin(); iter != container.end(); ++iter)
{
...
}
auto iter = container.begin();
auto endIter = container.end();
for (; iter != endIter; ++iter)
{
...
}
Eu costumo escrever:
SomeClass::SomeContainer::iterator Iter = m_SomeMemberContainerVar.begin(),
IterEnd = m_SomeMemberContainerVar.end();
for(...)
Acho que a segunda opção mais legível, como você não acabar com uma linha gigante. No entanto, Ferruccio traz um ponto sobre o alcance bom.