Pregunta

En este hilo, vemos ejemplos de buenos usos de goto en C o C ++. Está inspirado en una respuesta que la gente votó porque pensaron que estaba bromeando.

Resumen (etiqueta cambiada del original para que la intención sea aún más clara):

infinite_loop:

    // code goes here

goto infinite_loop;

Por qué es mejor que las alternativas:

  • Es específico. goto es el construcción del lenguaje que causa una Rama incondicional. Alternativas Depende del uso de estructuras. apoyando ramas condicionales, Con un degenerado siempre verdadero. condición.
  • La etiqueta documenta la intención sin comentarios adicionales.
  • El lector no tiene que escanear el código intermedio para las primeras break s (aunque todavía es posible para un hacker sin principios para simular continuar con un goto ).

Reglas:

  • Pretender que los gotophobes no lo hicieron ganar. Se entiende que lo anterior. No se puede utilizar en código real porque Va contra el lenguaje establecido.
  • Supongamos que todos hemos oído hablar de 'Goto considerado perjudicial' y saber ese goto puede ser usado para escribir código de espagueti.
  • Si no está de acuerdo con un ejemplo, criticarlo por mérito técnico solo ('porque a la gente no le gusta goto 'no es una razón técnica).

Veamos si podemos hablar de esto como adultos.

Editar

Esta pregunta parece terminada ahora. Generó algunas respuestas de alta calidad. Gracias a todos,  Especialmente aquellos que tomaron en serio mi pequeño ejemplo de bucle. La mayoría de los escépticos estaban preocupados.  Por la falta de alcance del bloque. Como @quinmars señaló en un comentario, siempre puede poner llaves alrededor del cuerpo de bucle. Observo que al pasar para (;;) y mientras que (true) no le dan las llaves de forma gratuita, ya sea (y omitirlos puede causar errores molestos). De todos modos, no desperdiciaré más. de su capacidad cerebral en este pequeño detalle: puedo vivir con el inofensivo e idiomático para (;;) y mientras que (verdadero) (también si quiero mantener mi trabajo).

Teniendo en cuenta las otras respuestas, veo que muchas personas ven goto como algo que siempre Hay que reescribir de otra manera. Por supuesto, puedes evitar un goto introduciendo un bucle,  una bandera adicional, una pila de if s anidadas, o lo que sea, pero ¿por qué no considerar si goto es ¿Quizás la mejor herramienta para el trabajo? Dicho de otra manera, ¿cuánta fealdad están las personas preparadas para soportar para evitar el uso de una función de lenguaje incorporada para el propósito previsto? Mi opinión es que incluso agregar una bandera es un precio demasiado alto para pagar. Me gustan mis variables para representar cosas en El problema o los dominios de solución. 'Solo para evitar un goto ' no se corta.

Aceptaré la primera respuesta que dio el patrón C para derivar a un bloque de limpieza. En mi opinión, este es el caso más sólido para un goto de todas las respuestas publicadas, ciertamente si lo mide por las contorsiones que tiene que atravesar un enemigo para evitarlo.

¿Fue útil?

Solución

Aquí hay un truco que he escuchado de personas que usan. Aunque nunca lo he visto en la naturaleza. Y solo se aplica a C porque C ++ tiene RAII para hacer esto de manera más idiomática.

void foo()
{
    if (!doA())
        goto exit;
    if (!doB())
        goto cleanupA;
    if (!doC())
        goto cleanupB;

    /* everything has succeeded */
    return;

cleanupB:
    undoB();
cleanupA:
    undoA();
exit:
    return;
}

Otros consejos

La necesidad clásica de GOTO en C es la siguiente

for ...
  for ...
    if(breakout_condition) 
      goto final;

final:

No hay una forma sencilla de salir de los bucles anidados sin un goto.

Aquí está mi ejemplo no tonto, (de Stevens APITUE) para llamadas al sistema Unix que pueden ser interrumpidas por una señal.

restart:
    if (system_call() == -1) {
        if (errno == EINTR) goto restart;

        // handle real errors
    }

La alternativa es un bucle degenerado. Esta versión se lee como en inglés " si la señal del sistema fue interrumpida por una señal, reiníciela " ;.

Si el dispositivo de Duff no necesita un goto, entonces usted tampoco debería hacerlo. ;)

void dsend(int count) {
    int n;
    if (!count) return;
    n = (count + 7) / 8;
    switch (count % 8) {
      case 0: do { puts("case 0");
      case 7:      puts("case 7");
      case 6:      puts("case 6");
      case 5:      puts("case 5");
      case 4:      puts("case 4");
      case 3:      puts("case 3");
      case 2:      puts("case 2");
      case 1:      puts("case 1");
                 } while (--n > 0);
    }
}

código anterior de la Wikipedia entrada .

Knuth ha escrito un documento "Programación estructurada con sentencias GOTO", puedes obtenerlo, por ejemplo. desde aquí . Encontrarás muchos ejemplos allí.

Muy común.

do_stuff(thingy) {
    lock(thingy);

    foo;
    if (foo failed) {
        status = -EFOO;
        goto OUT;
    }

    bar;
    if (bar failed) {
        status = -EBAR;
        goto OUT;
    }

    do_stuff_to(thingy);

OUT:
    unlock(thingy);
    return status;
}

El único caso que utilizo goto es para saltar hacia delante, generalmente fuera de bloques, y nunca en bloques. Esto evita el uso indebido de do {} while (0) y otras construcciones que aumentan el anidamiento, al tiempo que mantiene un código estructurado y legible.

No tengo nada en contra de gotos en general, pero puedo pensar en varias razones por las que no querrías usarlas para un bucle como mencionaste:

  • No limita el alcance, por lo que cualquier variable temporal que uses dentro no se liberará hasta más tarde.
  • No limita el alcance, por lo que podría provocar errores.
  • No limita el alcance, por lo tanto, no puede reutilizar los mismos nombres de variables más adelante en el código futuro en el mismo alcance.
  • No limita el alcance, por lo tanto, tiene la posibilidad de saltear una declaración de variable.
  • La gente no está acostumbrada a esto y hará que tu código sea más difícil de leer.
  • Los bucles anidados de este tipo pueden llevar a un código de espagueti, los bucles normales no conducirán a un código de espagueti.

Un buen lugar para usar un goto es en un procedimiento que puede abortar en varios puntos, cada uno de los cuales requiere varios niveles de limpieza. Los gotófobos siempre pueden reemplazar los gotos con código estructurado y una serie de pruebas, pero creo que esto es más sencillo porque elimina la sangría excesiva:

if (!openDataFile())
  goto quit;

if (!getDataFromFile())
  goto closeFileAndQuit;

if (!allocateSomeResources)
  goto freeResourcesAndQuit;

// Do more work here....

freeResourcesAndQuit:
   // free resources
closeFileAndQuit:
   // close file
quit:
   // quit!

@ fizzer.myopenid.com: su fragmento de código publicado es equivalente a lo siguiente:

    while (system_call() == -1)
    {
        if (errno != EINTR)
        {
            // handle real errors

            break;
        }
    }

Definitivamente prefiero este formulario.

A pesar de que he crecido hasta odiar este patrón con el tiempo, está integrado en la programación de COM.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error}
...
HRESULT SomeMethod(IFoo* pFoo) {
  HRESULT hr = S_OK;
  IfFailGo( pFoo->PerformAction() );
  IfFailGo( pFoo->SomeOtherAction() );
Error:
  return hr;
}

Aquí hay un ejemplo de un buen goto:

// No Code

He visto goto usado correctamente pero las situaciones son normalmente feas. Solo cuando el uso de goto en sí mismo es mucho menos peor que el original. @Johnathon Holland el problema es que tu versión es menos clara. La gente parece tener miedo de las variables locales:

void foo()
{
    bool doAsuccess = doA();
    bool doBsuccess = doAsuccess && doB();
    bool doCsuccess = doBsuccess && doC();

    if (!doCsuccess)
    {
        if (doBsuccess)
            undoB();
        if (doAsuccess)
            undoA();
    }
}

Y prefiero bucles como este, pero algunas personas prefieren while (true) .

for (;;)
{
    //code goes here
}

Mi queja sobre esto es que pierdes el alcance del bloque; cualquier variable local declarada entre los gotos permanece vigente si el bucle se rompe. (Tal vez estás asumiendo que el bucle se ejecuta para siempre; sin embargo, no creo que eso sea lo que el escritor de preguntas original estaba preguntando).

El problema del alcance es más que un problema con C ++, ya que algunos objetos pueden depender de que se llame a su dtor en los momentos apropiados.

Para mí, la mejor razón para usar goto es durante un proceso de inicialización de varios pasos en el que es vital que todas las acciones se eliminen si se produce un error, a la:

if(!foo_init())
  goto bye;

if(!bar_init())
  goto foo_bye;

if(!xyzzy_init())
  goto bar_bye;

return TRUE;

bar_bye:
 bar_terminate();

foo_bye:
  foo_terminate();

bye:
  return FALSE;

No uso goto yo mismo, sin embargo, sí trabajé con una persona que una vez los usaría en casos específicos. Si recuerdo correctamente, su razonamiento se refería a problemas de rendimiento: también tenía reglas específicas para cómo . Siempre en la misma función, y la etiqueta siempre fue DEBAJO de la instrucción goto.

#include <stdio.h>
#include <string.h>

int main()
{
    char name[64];
    char url[80]; /*The final url name with http://www..com*/
    char *pName;
    int x;

    pName = name;

    INPUT:
    printf("\nWrite the name of a web page (Without www, http, .com) ");
    gets(name);

    for(x=0;x<=(strlen(name));x++)
        if(*(pName+0) == '\0' || *(pName+x) == ' ')
        {
            printf("Name blank or with spaces!");
            getch();
            system("cls");
            goto INPUT;
        }

    strcpy(url,"http://www.");
    strcat(url,name);
    strcat(url,".com");

    printf("%s",url);
    return(0);
}

@Greg:

¿Por qué no haces tu ejemplo de esta manera?

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top