Aquí hay una muestra simplificada. Básicamente, verifica una cadena de una lista de cadenas. Si la verificación pasa, eliminará esa cadena ( filterStringOut(i);
), y ya no es necesario continuar ninguna otra verificación. Así continue
a la siguiente cadena.
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; */
}
}
}
}
¿Cómo se debe continuar el bucle externo desde el interior de un bucle anidado?
Mi mejor conjetura es usar goto
y colocar una etiqueta al final del bucle externo. Eso me llevó a hacer esta pregunta, dado lo tabú que goto
puede ser.
En el chat c ++ IRC, se sugirió que coloque los for
bucles en las funciones bool, que devuelven verdadero si se pasa un cheque. así
if ( containsExclude(s)) continue;
if (!containsInclude(s)) continue;
o que simplemente creo un booleano local, lo configuro en verdadero break
, verifico bool y continúo si es verdadero.
Dado que estoy usando esto en un analizador, realmente necesito priorizar el rendimiento en este ejemplo. ¿Es esta una situación en la goto
que todavía es útil, o es un caso en el que necesito reestructurar mi código?
goto
, a pesar de su mala reputación. No temas a los nombres, teme a los conceptos.Respuestas:
No anides: convierte a funciones en su lugar. Y
true
haga que esas funciones regresen si realizan su acción y se pueden omitir los pasos siguientes;false
de otra manera. De esa manera, evita por completo el problema de cómo salir de un nivel, continuar dentro de otro, etc., ya que simplemente encadena las llamadas con||
(esto supone que C ++ deja de procesar una expresión en untrue
; creo que sí).Por lo tanto, su código podría terminar pareciéndose a lo siguiente (no he escrito C ++ en años, por lo que probablemente contenga errores de sintaxis, pero debería darle la idea general):
fuente
ParsingTools::filterStrings
) llame a lafilterStringOut(i)
función, como se muestra en la respuesta de dagnelies.Desde una perspectiva más de vista de pájaro, refactorizaría el código para que se vea así ... (en pseudocódigo, hace demasiado tiempo toqué C ++)
Esto es más limpio en mi humilde opinión porque separa claramente lo que constituye una cadena adecuada y lo que haces cuando no lo es.
Incluso podría ir un paso más allá y utilizar métodos de filtro integrados como
myProperStrings = allMyStrings.filter(isProperString)
fuente
Realmente me gusta cómo comienza @dagnelies . Corto y al grano. Un buen uso de la abstracción de alto nivel. Solo estoy ajustando su firma y evitando un negativo innecesario.
Sin embargo, me gusta cómo @DavidArno desglosa las pruebas de requisitos como funciones individuales. Claro que todo se vuelve más largo, pero cada función es maravillosamente pequeña. Sus nombres evitan la necesidad de comentarios para explicar lo que son. Simplemente no me gusta que asuman la responsabilidad adicional de llamar
filterStringOut()
.Por cierto, sí, C ++ detendrá la evaluación de una
||
cadenatrue
siempre que no haya sobrecargado el||
operador. Esto se llama evaluación de cortocircuito . Pero esta es una micro optimización trivial que puede ignorar mientras lee el código, siempre que las funciones no tengan efectos secundarios (como las que se muestran a continuación).Lo siguiente debería aclarar la definición de una cadena de rechazo sin arrastrarlo a través de detalles innecesarios:
Aliviado de la necesidad de llamar a
filterStringOut()
los requisitos, las funciones de prueba se acortan y sus nombres son mucho más simples. También he puesto todo de lo que dependen en su lista de parámetros para que sea más fácil entenderlos sin mirar dentro.Agregué
requiredSubstring
yforbiddenSubstring
para los humanos. ¿Te retrasarán? Prueba y descúbrelo. Es más fácil hacer que el código legible sea realmente rápido que hacer que el código optimizado prematuramente sea legible o realmente rápido.Si las funciones te ralentizan, busca funciones en línea antes de someter a los humanos a un código ilegible. Nuevamente, no asumas que esto te dará velocidad. Prueba.
Creo que encontrarás cualquiera de estos más legibles que anidados para bucles. Esos, combinados con los
if
's, estaban comenzando a darle un verdadero antipatrón de flecha . Creo que la lección aquí es que las funciones pequeñas son algo bueno.fuente
! isProperString
más queisImproperString
intencional. Tiendo a evitar negaciones en los nombres de funciones. Imagine que necesita verificar si es una cadena adecuada más adelante, necesitará!isImproperString
cuál es, en mi humilde opinión, más propenso a la confusión debido a la doble negación.Simplemente use una lambda para el predicado, y luego use el poder de algoritmos estándar y cortocircuitos. No es necesario ningún flujo de control enrevesado o exótico:
fuente
También existe la opción de hacer que el contenido del bucle externo (el que desea continuar) sea lambda , y simplemente usar
return
.Es sorprendentemente fácil si conoces lambdas; básicamente comienza su interior de bucle
[&]{
y termina con}()
; dentro puedes usarloreturn;
en cualquier momento para dejarlo:fuente
continue
ybreak
deben ser reemplazados porreturn
. Parece que su código deja el primer lugar (que usacontinue
) sin cambios, pero eso también debe cambiarse, porque el código está dentro de la lambda y lacontinue
declaración no pudo encontrar un alcance que sea un bucle.Creo que @dganelies tiene la idea correcta como punto de partida, pero creo que consideraría ir un paso más allá: escribir una función genérica que pueda llevar a cabo el mismo patrón para (casi) cualquier contenedor, criterio y acción:
Su
filterStrings
entonces simplemente definir los criterios, y pasar la acción apropiada:Por supuesto, también hay otras formas de abordar ese problema básico. Por ejemplo, usando la biblioteca estándar, parece querer algo en el mismo orden general que
std::remove_if
.fuente
Varias respuestas sugieren un refactor principal del código. Probablemente no sea un mal camino, pero quería proporcionar una respuesta que esté más en línea con la pregunta en sí.
Regla # 1: Perfil antes de optimizar
Siempre perfile los resultados antes de intentar una optimización. Puede que pierda una gran cantidad de tiempo si no lo hace.
Habiendo dicho eso...
Tal como están las cosas, he probado personalmente este tipo de código en MSVC. Los booleanos son el camino a seguir. Nombra el booleano como algo semánticamente significativo
containsString
.En MSVC (2008), en modo de lanzamiento (configuración típica del optimizador), el compilador optimizó un bucle similar a exactamente el mismo conjunto de códigos de operación que la
goto
versión. Fue lo suficientemente inteligente como para ver que el valor del booleano estaba directamente relacionado con el flujo de control, y eludió todo. No he probado gcc, pero supongo que puede hacer tipos similares de optimización.Esto tiene la ventaja
goto
de no plantear ninguna preocupación por parte de los puristas que considerangoto
perjudicial, sin sacrificar el rendimiento de una sola instrucción.fuente