Aqui está uma amostra simplificada. Basicamente, ele verifica uma string de uma lista de strings. Se a verificação for aprovada, ela removerá a string ( filterStringOut(i);
) e não será mais necessário continuar com outras verificações. Assim, continue
para a próxima string.
void ParsingTools::filterStrings(QStringList &sl)
{
/* Filter string list */
QString s;
for (int i=0; i<sl.length(); i++) {
s = sl.at(i);
// Improper length, remove
if (s.length() != m_Length) {
filterStringOut(i);
continue; // Once removed, can move on to the next string
}
// Lacks a substring, remove
for (int j=0; j<m_Include.length(); j++) {
if (!s.contains(m_Include.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
// Contains a substring, remove
for (int j=0; j<m_Exclude.length(); j++) {
if (s.contains(m_Exclude.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
}
}
Como se deve continuar o loop externo de dentro de um loop aninhado?
Meu melhor palpite é usar goto
e colocar um rótulo no final do loop externo. Isso me levou a fazer essa pergunta, dado o quão tabu goto
pode ser.
No bate-papo do c ++ IRC, foi sugerido que eu coloque os for
loops nas funções bool, que retornam true se uma verificação for aprovada. portanto
if ( containsExclude(s)) continue;
if (!containsInclude(s)) continue;
ou simplesmente crie um booleano local, defina-o como true break
, verifique bool e continue se true.
Como estou usando isso em um analisador, na verdade, preciso priorizar o desempenho neste exemplo. É uma situação em que goto
ainda é útil ou é necessário reestruturar meu código?
goto
, apesar de sua má reputação. Não tema nomes - tema conceitos.Respostas:
Não aninhe: converta para funções. E faça com que essas funções retornem
true
se eles executarem sua ação e as etapas subsequentes puderem ser ignoradas;false
de outra forma. Dessa forma, você evita completamente todo o problema de como sair de um nível, continuar dentro de outro, etc. como você apenas encadeia as chamadas||
(isso pressupõe que o C ++ interrompe o processamento de uma expressão em umtrue
; acho que sim).Portanto, seu código pode acabar parecido com o seguinte (não escrevo C ++ há anos, portanto, provavelmente contém erros de sintaxe, mas deve fornecer uma idéia geral):
fonte
ParsingTools::filterStrings
) chame afilterStringOut(i)
função, conforme mostrado na resposta de dagnelies.De uma perspectiva mais detalhada, refatoraria o código para que fique assim ... (em pseudo-código, faz muito tempo que toquei em C ++)
Este é o limpador IMHO, porque separa claramente o que constitui uma string adequada e o que você faz quando não é.
Você poderia dar um passo adiante e usar métodos de filtro internos, como
myProperStrings = allMyStrings.filter(isProperString)
fonte
Eu realmente gosto de como @dagnelies começa . Curto e direto ao ponto. Um bom uso da abstração de alto nível. Estou apenas ajustando sua assinatura e evitando um negativo desnecessário.
No entanto, gosto de como o @DavidArno divide os testes de requisitos como funções individuais. Claro que a coisa toda fica mais longa, mas todas as funções são maravilhosamente pequenas. Seus nomes evitam a necessidade de comentários para explicar o que são. Só não gosto que eles assumam a responsabilidade extra de ligar
filterStringOut()
.A propósito, sim, o C ++ interromperá a avaliação de uma
||
cadeiatrue
enquanto você não sobrecarregar o||
operador. Isso é chamado de avaliação de curto-circuito . Mas esta é uma micro otimização trivial que você pode ignorar ao ler o código, desde que as funções sejam livres de efeitos colaterais (como as abaixo).O seguinte deve tornar clara a definição de uma sequência de rejeição sem arrastar você por detalhes desnecessários:
Aliviado da necessidade de chamar
filterStringOut()
as funções de teste de requisitos, fica mais curto e seus nomes são muito mais simples. Também coloquei tudo o que eles dependem em sua lista de parâmetros para facilitar a compreensão sem olhar para dentro.Eu adicionei
requiredSubstring
eforbiddenSubstring
para os humanos. Eles vão te atrasar? Teste e descubra. É mais fácil tornar o código legível realmente rápido, em seguida, tornar o código prematuramente otimizado legível ou realmente rápido.Se as funções ficarem mais lentas, procure funções inline antes de submeter os humanos a códigos ilegíveis. Novamente, não assuma que isso lhe dará velocidade. Teste.
Acho que você encontrará qualquer um desses itens mais legíveis do que aninhados para loops. Aqueles, combinados com os
if
, estavam começando a dar a você um verdadeiro anti-padrão de flechas . Acho que a lição aqui é que pequenas funções são uma coisa boa.fonte
! isProperString
queisImproperString
foi intencional. Eu costumo evitar negações nos nomes das funções. Imagine que você precisa verificar se é realmente uma sequência adequada mais tarde, você precisará do!isImproperString
que é IMHO mais propenso a confusão por causa da dupla negação.Basta usar um lambda para o predicado e, em seguida, usar o poder dos algoritmos padrão e do curto-circuito. Não há necessidade de qualquer fluxo de controle complicado ou exótico:
fonte
Também há a opção de tornar o conteúdo do loop externo (aquele que você deseja continuar) um lambda e simplesmente usar
return
.É surpreendentemente fácil se você conhece lambdas; você basicamente inicia o interior do loop
[&]{
e termina com}()
; dentro você pode usarreturn;
a qualquer momento para deixá-lo:fonte
continue
ebreak
devem ser substituídos porreturn
. Seu código parece deixar o primeiro lugar (que usacontinue
) inalterado, mas isso também deve ser alterado, porque o código está dentro do lambda e acontinue
instrução não conseguiu encontrar um escopo que é um loop.Eu acho que @dganelies tem a idéia certa como ponto de partida, mas acho que consideraria um passo adiante: escreva uma função genérica que possa executar o mesmo padrão para (quase) qualquer contêiner, critério e ação:
Você
filterStrings
apenas definiria os critérios e passaria a ação apropriada:Obviamente, existem outras maneiras de abordar esse problema básico também. Por exemplo, usando a biblioteca padrão, você parece querer algo na mesma ordem geral que
std::remove_if
.fonte
Várias respostas sugerem um grande refator do código. Provavelmente, esse não é um caminho ruim, mas eu gostaria de fornecer uma resposta que esteja mais de acordo com a pergunta em si.
Regra # 1: perfil antes de otimizar
Sempre analise os resultados antes de tentar uma otimização. Você pode perder muito tempo se não o fizer.
Dito isto ...
No momento, testei pessoalmente esse tipo de código no MSVC. Os booleanos são o caminho a percorrer. Nomeie o booleano como algo semanticamente significativo
containsString
.No MSVC (2008), no modo de liberação (configurações típicas do otimizador), o compilador otimizou um loop semelhante até exatamente o mesmo conjunto de códigos de operação da
goto
versão. Foi inteligente o suficiente para ver que o valor do booleano estava diretamente ligado ao controle do fluxo e eliminava tudo. Não testei o gcc, mas presumo que ele possa fazer tipos semelhantes de otimização.Isso tem a vantagem
goto
de simplesmente não levantar nenhuma preocupação dos puristas que consideramgoto
prejudiciais, sem sacrificar o desempenho de uma única instrução.fonte