Cómo evitar el uso de goto y romper bucles anidados de manera eficiente [cerrado]


Diría que es un hecho que usar goto se considera una mala práctica cuando se trata de programar en C/C++.

Sin embargo, dado el siguiente código

for (i = 0; i < N; ++i) 
{
    for (j = 0; j < N; j++) 
    {
        for (k = 0; k < N; ++k) 
        {
            ...
            if (condition)
                goto out;
            ...
        }
    }
}
out:
    ...

Me pregunto cómo lograr el mismo comportamiento de manera eficiente sin usar goto. Lo que quiero decir es que podríamos hacer algo como comprobar condition al final de cada bucle, por ejemplo, pero AFAIK goto generará solo una instrucción de ensamblaje que será un jmp. Así que esta es la forma más eficiente de hacer esto que puedo pensar de.

¿Hay alguna otra que se considere una buena práctica? ¿Me equivoco cuando digo que se considera una mala práctica usar goto? Si lo estoy, ¿sería éste uno de esos casos en los que es bueno usarlo?

Gracias

 31
Author: fuz, 2018-05-25

14 answers

La mejor versión (imo) no goto se vería algo como esto:

void calculateStuff()
{
    // Please use better names than this.
    doSomeStuff();
    doLoopyStuff();
    doMoreStuff();
}

void doLoopyStuff()
{
    for (i = 0; i < N; ++i) 
    {
        for (j = 0; j < N; j++) 
        {
            for (k = 0; k < N; ++k) 
            {
                /* do something */

                if (/*condition*/)
                    return; // Intuitive control flow without goto

                /* do something */
            }
        }
    }
}

Dividir esto también es probablemente una buena idea porque le ayuda a mantener sus funciones cortas, su código legible (si nombra las funciones mejor que yo) y dependencias bajas.

 46
Author: Max Langhof,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:49:37

Si tiene bucles profundamente anidados como ese y debe romperse, creo que goto es la mejor solución. Algunos lenguajes (no C) tienen una instrucción break(N) que saldrá de más de un bucle. La razón por la que C no lo tiene es que es incluso peor que a goto: tienes que contar los bucles anidados para averiguar qué hace, y es vulnerable a que alguien venga más tarde y agregue o elimine un nivel de anidamiento, sin notar que la ruptura hay que ajustar el recuento.

gotos son generalmente mal visto. Usar un goto aquí no es una buena solución; es simplemente el menor de varios males.

En la mayoría de los casos, la razón por la que tienes que salir de un bucle profundamente anidado es porque estás buscando algo y lo has encontrado. En ese caso (y como varios otros comentarios y respuestas han sugerido), prefiero mover el bucle anidado a su propia función. En ese caso, un return fuera del bucle interno logra su tarea muy limpiamente.

(Hay quienes dicen que las funciones siempre deben regresar al final, no desde el medio. Esas personas dirían que la solución easy break-it-out-to-a-function es por lo tanto inválida, y forzarían el uso de la misma(s) técnica (s) incómoda (s) break-out-of-the-inner-loop, incluso cuando la búsqueda se dividió a su propia función. Personalmente, creo que esas personas están equivocadas, pero su kilometraje puede variar.)

Si insiste en no usar un goto, y si insistes en no usar una función separada con un retorno temprano, entonces sí, puedes hacer algo como mantener variables de control booleanas adicionales y probarlas redundantemente en la condición de control de cada bucle anidado, pero eso es solo una molestia y un desastre. (Es uno de los mayores males que yo decía usando un simple goto es menor que.)

 21
Author: Steve Summit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 16:16:27

Creo que gotoes una cosa perfectamente sana para hacer aquí, y es uno de sus casos de uso excepcionales según las Directrices C++ Core.

Sin embargo, tal vez otra solución a considerar es un IIFE lambda. En mi opinión, esto es un poco más elegante que declarar una función separada!

[&] {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; ++k)
                if (condition)
                    return;
}();

Gracias a JohnMcPineapple en reddit por esta sugerencia!

 16
Author: ricco19,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 16:17:39

En este caso no quieres evitar usar goto.

En general se debe evitar el uso de goto, sin embargo hay excepciones a esta regla, y su caso es un buen ejemplo de una de ellas.

Veamos las alternativas:

for (i = 0; i < N; ++i) {
    for (j = 0; j < N; j++) {
        for (k = 0; k < N; ++k) {
            ...
            if (condition)
                break;
            ...
        }
        if (condition)
            break;
    }
    if (condition)
        break;
}

O:

int flag = 0
for (i = 0; (i < N) && !flag; ++i) {
    for (j = 0; (j < N) && !flag; j++) {
        for (k = 0; (k < N) && !flag; ++k) {
            ...
            if (condition) {
                flag = 1
                break;
            ...
        }
    }
}

Ninguno de ellos es tan conciso o tan legible como la versión goto.

El uso de un goto se considera aceptable en los casos en que solo está saltando hacia adelante (no hacia atrás) y hacerlo hace que su código sea más legible y comprensible.

Si por otro lado se usa goto para saltar en ambas direcciones, o para saltar a un ámbito que potencialmente podría omitir la inicialización de variables, eso sería malo.

Este es un mal ejemplo de goto:

int x;
scanf("%d", &x);
if (x==4) goto bad_jump;

{
    int y=9;

// jumping here skips the initialization of y
bad_jump:

    printf("y=%d\n", y);
}

Un compilador de C++ lanzará un error aquí porque el goto salta sobre la inicialización de y. Sin embargo, los compiladores de C compilarán esto, y el código anterior invocará comportamiento indefinido al intentar imprimir y que no se inicializará si se produce el goto.

Otro ejemplo de uso adecuado de goto es en el manejo de errores:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        goto end1;
    }
    char *p2 = malloc(10);
    if (!p2) {
        goto end2;
    }
    char *p3 = malloc(10);
    if (!p3) {
        goto end3;
    }
    // do something with p1, p2, and p3

end3:
    free(p3);
end2:
    free(p2);
end1:
    free(p1);
}

Esto realiza toda la limpieza al final de la función. Compare esto con la alternativa:

void f()
{
    char *p1 = malloc(10);
    if (!p1) {
        return;
    }
    char *p2 = malloc(10);
    if (!p2) {
        free(p1);
        return;
    }
    char *p3 = malloc(10);
    if (!p3) {
        free(p2);
        free(p1);
        return;
    }
    // do something with p1, p2, and p3

    free(p3);
    free(p2);
    free(p1);
}

Donde la limpieza se realiza en varios lugares. Si luego agrega más recursos que necesitan ser limpiados, debe recordar agregar la limpieza en todos estos lugares más la limpieza de los recursos obtenidos anteriormente.

El ejemplo anterior es más relevante para C que para C++, ya que en este último caso puede usar clases con destructores adecuados y punteros inteligentes para evitar la limpieza manual.

 11
Author: dbush,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 14:32:34

Alternativa - 1

Puedes hacer algo como lo siguiente:{[14]]}

  1. Establece una variable bool al principio isOkay = true
  2. Todas tus condiciones forde bucle , agrega una condición adicional isOkay == true
  3. Cuando su condición personalizada se cumple/ falla, establezca isOkay = false.

Esto hará que tus bucles se detengan. Sin embargo, una variable bool adicional a veces sería útil.

bool isOkay = true;
for (int i = 0; isOkay && i < N; ++i)
{
    for (int j = 0; isOkay && j < N; j++)
    {
        for (int k = 0; isOkay && k < N; ++k)
        {
            // some code
            if (/*your condition*/)
                 isOkay = false;
        }
     }
}

Alternativa - 2

En segundo lugar. si las iteraciones de bucle anteriores están en una función, la mejor opción es return result, cuando se cumpla la condición personalizada.

bool loop_fun(/* pass the array and other arguments */)
{
    for (int i = 0; i < N ; ++i)
    {
        for (int j = 0; j < N ; j++)
        {
            for (int k = 0; k < N ; ++k)
            {
                // some code
                if (/* your condition*/)
                    return false;
            }
        }
    }
    return true;
}
 5
Author: JeJo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-30 20:40:22

Las Lambdas te permiten crear ámbitos locales:

[&]{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return;
        ...
      }
    }
  }
}();

Si también quieres la habilidad de devolver fuera de ese alcance:

if (auto r = [&]()->boost::optional<RetType>{
  for (i = 0; i < N; ++i) 
  {
    for (j = 0; j < N; j++) 
    {
      for (k = 0; k < N; ++k) 
      {
        ...
        if (condition)
          return {};
        ...
      }
    }
  }
}()) {
  return *r;
}

Donde regresar {} o boost::nullopt es un "break", y devolver un valor devuelve un valor desde el ámbito envolvente.

Otro enfoque es:

for( auto idx : cube( {0,N}, {0,N}, {0,N} ) {
  auto i = std::get<0>(idx);
  auto j = std::get<1>(idx);
  auto k = std::get<2>(idx);
}

Donde generamos un iterable sobre las 3 dimensiones y lo hacemos un bucle anidado profundo de 1. Ahora break funciona bien. Tienes que escribir cube.

In c++17 this se convierte en

for( auto[i,j,k] : cube( {0,N}, {0,N}, {0,N} ) ) {
}

Lo cual es bueno.

Ahora, en una aplicación en la que se supone que debe ser sensible, looping sobre un gran rango dimensional 3 a nivel de flujo de control primiary es a menudo una mala idea. Puede enhebrarlo, pero incluso entonces termina con el problema de que el hilo se ejecuta demasiado largo. Y la mayoría de las iteraciones grandes de 3 dimensiones con las que he jugado pueden beneficiarse del uso de subprocesos de tareas secundarias.

Con ese fin, terminarás queriendo categorizar tu operación basado en qué tipo de datos accede, luego pase su operación a algo que programe la iteración para usted.

auto work = do_per_voxel( volume,
  [&]( auto&& voxel ) {
    // do work on the voxel
    if (condition)
      return Worker::abort;
    else
      return Worker::success;
  }
);

Entonces el flujo de control involucrado entra en la función do_per_voxel.

do_per_voxel no va a ser un simple bucle desnudo, sino más bien un sistema para reescribir las tareas por voxel en tareas por línea de exploración (o incluso tareas por plano dependiendo de cuán grandes sean los planos/líneas de exploración en tiempo de ejecución (!)) luego envíelos a su vez a un programador de tareas administrado de grupo de subprocesos, coser los controladores de tarea resultantes, y devolver un futuro-como work que puede ser esperado o utilizado como un disparador de continuación para cuando el trabajo está hecho.

Y a veces solo usas goto. O se rompen manualmente las funciones para subloops. O usa banderas para salir de la recursión profunda. O pones todo el bucle de 3 capas en su propia función. O puede componer los operadores de bucle utilizando una biblioteca de mónadas. O puedes lanzar una excepción (!) y atraparlo.

La respuesta a casi cada pregunta en c++ es "depende". El alcance del problema y el número de técnicas que tiene disponibles es grande, y los detalles del problema cambian los detalles de la solución.

 4
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 14:16:36

Divide tus bucles for en funciones. Hace las cosas mucho más fáciles de entender porque ahora se puede ver lo que cada bucle está haciendo realmente.

bool doHerpDerp() {
    for (i = 0; i < N; ++i) 
    {
        if (!doDerp())
            return false;
    }
    return true;
}

bool doDerp() {
    for (int i=0; i<X; ++i) {
        if (!doHerp())
            return false;
    }
    return true;
}

bool doHerp() {
    if (shouldSkip)
        return false;
    return true;
}
 3
Author: UKMonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:26:27

¿Hay alguna otra que se considere una buena práctica? Me equivoco cuando Digo que se considera una mala práctica usar goto?

goto puede ser mal utilizado y sobreutilizado, pero no veo ninguno de los dos en su ejemplo. La ruptura de un bucle profundamente anidado se expresa más claramente por un simple goto label_out_of_the_loop;.

Es una mala práctica usar muchos goto s que saltan a diferentes etiquetas, pero en tales casos no es la palabra clave goto la que hace que su código sea malo. Es el hecho de que usted está saltando en el código por lo que es difícil de seguir que hace que sea malo. Sin embargo, si necesita un solo salto de bucles anidados, ¿por qué no usar la herramienta que se hizo exactamente para ese propósito?

Para usar una analogía hecha de la nada: Imagina que vives en un mundo donde en el pasado era de moda clavar clavos en las paredes. En los últimos tiempos se hizo más fashinable para taladrar tornillos en las paredes con destornilladores y martillos están completamente fuera de moda. Ahora considera que tienes que (a pesar de ser un poco viejo-fashinoned) consigue un clavo en una pared. No debes abstenerte de usar un martillo para hacer eso, pero tal vez deberías preguntarte si realmente necesitas un clavo en la pared en lugar de un tornillo.

(Por si no está claro: El martillo es goto y el clavo en la pared es un salto de un bucle anidado mientras que el tornillo en la pared estaría usando funciones para evitar el anidamiento profundo en conjunto;)

 3
Author: user463035818,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:48:20

Una forma posible es asignar un valor booleano a una variable que representa el estado. Este estado se puede probar más tarde usando una instrucción condicional "IF" para otros propósitos más adelante en el código.

 2
Author: Chigozie Orunta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:20:18

En cuanto a su comentario sobre la eficiencia, compilar las dos opciones en el modo release en visual studio 2017 produce exactamente el mismo ensamblaje.

for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                goto end;

            }
        }
    }
}
end:;

Y con una bandera.

bool done = false;
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                done = true;
                break;
            }
        }
        if (done) break;
    }
    if (done) break;
}

Ambos producen..

xor         edx,edx  
xor         ecx,ecx  
xor         eax,eax  
cmp         edx,1  
jne         main+15h (0C11015h)  
cmp         ecx,2  
jne         main+15h (0C11015h)  
cmp         eax,3  
je          main+27h (0C11027h)  
inc         eax  
cmp         eax,5  
jl          main+6h (0C11006h)  
inc         ecx  
cmp         ecx,5  
jl          main+4h (0C11004h)  
inc         edx  
cmp         edx,5  
jl          main+2h (0C11002h)    

Así que no hay ganancia. Otra opción si está usando un compilador moderno de c++ es envolverlo en una lambda.

[](){
for (int i = 0; i < 5; ++i)
{
    for (int j = 0; j < 5; ++j)
    {
        for (int k = 0; k < 5; ++k)
        {
            if (i == 1 && j == 2 && k == 3) {
                return;
            }
        }

    }
}
}();

Nuevamente esto produce exactamente el mismo ensamblaje. Personalmente creo que usar goto en su ejemplo es perfectamente aceptable. Está claro lo que está sucediendo con cualquier otra persona, y hace que el código sea más conciso. Podría decirse que la lambda es igualmente concisa.

 2
Author: rmawatson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:45:52

Específico

IMO, en este ejemplo específico, creo que es importante notar una funcionalidad común entre sus bucles. (Ahora sé que tu ejemplo no es necesariamente literal aquí, pero ten paciencia conmigo por un segundo) como cada bucle itera N veces, puedes reestructurar tu código de la siguiente manera:

Ejemplo

int max_iterations = N * N * N;
for (int i = 0; i < max_iterations; i++)
{
    /* do stuff, like the following for example */
    *(some_ptr + i) = 0; // as opposed to *(some_3D_ptr + i*X + j*Y + Z) = 0;
    // some_arr[i] = 0; // as oppose to some_3D_arr[i][j][k] = 0;
}

Ahora, es importante recordar que todos los bucles, mientras que para o no, son realmente solo azúcar sintática para el paradigma if-goto. Coincido con los otros que debería tener una función devolver el resultado, sin embargo, quería mostrar un ejemplo como el anterior en el que puede que no sea el caso. Por supuesto, yo marcaría lo anterior en una revisión de código, pero si reemplazara lo anterior con un goto, lo consideraría un paso en la dirección equivocada. (NOTA Make Asegúrese de que puede encajar de forma fiable en el tipo de datos deseado)

General

Ahora, como respuesta general, las condiciones de salida para su bucle pueden no ser las mismas cada vez (como el post en cuestión). Como regla general, saque tantas operaciones innecesarias de sus bucles (multiplicaciones, etc.).) en la medida de lo posible, mientras que los compiladores son cada vez más inteligentes todos los días, no hay reemplazo para escribir código eficiente y legible.

Ejemplo

/* matrix_length: int of m*n (row-major order) */
int num_squared = num * num;
for (int i = 0; i < matrix_length; i++)
{
    some_matrix[i] *= num_squared; // some_matrix is a pointer to an array of ints of size matrix_length
}

En lugar de escribir *= num * num, ya no tenemos que confiar en el compilador para optimizar esto para nosotros (aunque cualquier buen compilador debería hacerlo). Así que cualquier bucle doblemente o triplemente anidado que realice la funcionalidad anterior también beneficiaría no solo su código, pero IMO escribir código limpio y eficiente de su parte. En el primer ejemplo, podríamos haber tenido *(some_3D_ptr + i*X + j*Y + Z) = 0;! ¿Confiamos en que el compilador optimice out i*X y j*Y, de modo que no se calculen cada iteración?

bool check_threshold(int *some_matrix, int max_value)
{
    for (int i = 0; i < rows; i++)
    {
        int i_row = i*cols; // no longer computed inside j loop unnecessarily.
        for (int j = 0; j < cols; j++)
        {
            if (some_matrix[i_row + j] > max_value) return true;
        }
    }
    return false;
}

¡Qué asco! ¿Por qué no estamos usando clases proporcionadas por el STL o una biblioteca como Boost? (debemos estar haciendo algún código de bajo nivel / alto rendimiento aquí). Ni siquiera pude escribir una versión 3D, debido a la complejidad. A pesar de que tenemos la mano optimizado algo, incluso puede ser mejor usar # pragma unroll o sugerencias similares de preprocesador si su compilador lo permite.

Conclusión

En general, cuanto mayor sea el nivel de abstracción que puede utilizar, mejor, sin embargo, si por ejemplo aliasing una matriz de orden mayor de fila 1-Dimensional de enteros a una matriz 2-Dimensional hace que su flujo de código más difícil de entender/extender, ¿vale la pena? Del mismo modo, que también puede ser un indicador para hacer algo en su propia función. Espero que, dadas estas ejemplos, se puede ver que diferentes paradigmas se requieren en diferentes lugares, y es su trabajo como programador para averiguar eso. No te vuelvas loco con lo anterior, pero asegúrate de saber lo que significan, cómo usarlos y cuándo se requieren, y lo más importante, asegúrate de que las otras personas que usan tu base de código también sepan lo que son y no tengan reparos al respecto. ¡Buena suerte!

 2
Author: jfh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 15:03:45
bool meetCondition = false;
for (i = 0; i < N && !meetCondition; ++i) 
{
    for (j = 0; j < N && !meetCondition; j++) 
    {
        for (k = 0; k < N && !meetCondition; ++k) 
        {
            ...
            if (condition)
                meetCondition = true;
            ...
        }
    }
}
 1
Author: nameless1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:22:07

Ya hay varias respuestas excelentes que te dicen cómo puedes refactorizar tu código, así que no las repetiré. Ya no hay necesidad de codificar de esa manera para la eficiencia; la pregunta es si es poco elegante. (Bien, un refinamiento sugeriré: si sus funciones auxiliares solo están destinadas a ser utilizadas dentro del cuerpo de esa función, puede ayudar al optimizador declarándolas static, para que sepa con certeza que la función no tiene enlace externo y lo hará nunca se llame desde ningún otro módulo, y la pista inline no puede hacer daño. Sin embargo, las respuestas anteriores dicen que, cuando se usa una lambda, los compiladores modernos no necesitan tales sugerencias.)

Voy a desafiar un poco el encuadre de la pregunta. Tienes razón en que la mayoría de los programadores tienen un tabú en contra de usar goto. Esto, en mi opinión, ha perdido de vista el propósito original. Cuando Edsger Dijkstra escribió, "Ir a la Declaración Considerada Perjudicial," había una razón específica por la que pensaba así: el uso" desenfrenado " de go to hace que sea demasiado difícil razonar formalmente sobre el estado actual del programa, y qué condiciones deben ser verdaderas actualmente, en comparación con el flujo de control de llamadas a funciones recursivas (que él prefirió) o bucles iterativos (que él aceptó). Concluyó:

La declaración go to tal como está es simplemente demasiado primitiva; es demasiado una invitación para hacer un lío del programa de uno. Uno puede considerar y apreciar las cláusulas consideradas como limitantes de su uso. Yo no pretendo que las cláusulas mencionadas son exhaustivas en el sentido de que satisfarán todas las necesidades, pero cualesquiera que sean las cláusulas sugeridas (por ejemplo, cláusulas de aborto) deben satisfacer el requisito de que se pueda mantener un sistema de coordenadas independiente del programador para describir el proceso de una manera útil y manejable.

Muchos lenguajes de programación similares a C, por ejemplo Rust y Java, tienen una "cláusula adicional considerada como un puente para su uso", la break a una etiqueta. Aún más la sintaxis restringida podría ser algo así como break 2 continue; para romper dos niveles del bucle anidado y reanudar en la parte superior del bucle que los contiene. Esto no presenta más problema que un estilo C break a lo que Dijkstra quería hacer: definir una descripción concisa del estado del programa que los programadores pueden rastrear en sus cabezas o que un analizador estático encontraría manejable.

Restringir goto a construcciones como esta lo convierte simplemente en un salto renombrado a una etiqueta. El resto el problema con esto es que el compilador y el programador no necesariamente saben que solo vas a usarlo de esta manera.

Si hay una condición post importante que se mantiene después del bucle, y su preocupación con goto es la misma que la de Dijkstra, podría considerar indicarla en un breve comentario, algo como // We have done foo to every element, or encountered condition and stopped. Que aliviaría el problema para los humanos, y un analizador estático debería funcionar bien.

 1
Author: Davislor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-30 21:01:35

La mejor solución es poner los bucles en una función y luego return de esa función.

Esto es esencialmente lo mismo que tu goto ejemplo, pero con el beneficio masivo que evitas tener otro goto debate.

Pseudo código simplificado:

bool function (void)
{
  bool result = something;


  for (i = 0; i < N; ++i) 
    for (j = 0; j < N; j++) 
      for (k = 0; k < N; ++k) 
        if (condition)
          return something_else;
  ...
  return result;
}

Otro beneficio aquí es que puede actualizar de bool a un enum si se encuentra con más de 2 escenarios. Realmente no se puede hacer eso con goto de una manera legible. El momento en que empiezas a usar múltiples gotos y múltiples etiquetas, es el momento en que abrazas la codificación espagueti. Sí, incluso si solo ramifica hacia abajo, no será agradable de leer y mantener.

Notablemente, si tiene 3 bucles anidados para, eso puede ser una indicación de que debe intentar dividir su código en varias funciones y entonces toda esta discusión podría ni siquiera ser relevante.

 0
Author: Lundin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-25 13:34:44