Domanda

In questo thread, esaminiamo esempi di buoni usi di goto in C o C ++. È ispirato a una risposta a cui la gente ha votato perché hanno pensato che stavo scherzando.

Riepilogo (etichetta modificata dall'originale per rendere ancora più chiaro l'intento):

infinite_loop:

    // code goes here

goto infinite_loop;

Perché è meglio delle alternative:

  • È specifico. goto è il costrutto del linguaggio che causa un ramo incondizionato. alternative dipende dall'uso delle strutture sostenere i rami condizionali, con un degenerato sempre vero condizioni.
  • L'etichetta documenta l'intento senza commenti extra.
  • Il lettore non deve scansionare il file codice intermedio per break precoci (anche se è ancora possibile per un hacker senza principi da simulare continua con un iniziale ).

Regole:

  • Fai finta che i gotofobi non lo abbiano fatto vincere. Resta inteso che quanto sopra non può essere utilizzato nel codice reale perché va contro il linguaggio consolidato.
  • Supponiamo che ne abbiamo tutti sentito parlare 'Vai considerato dannoso' e sapere quel goto può essere usato per scrivere codice spaghetti.
  • Se non sei d'accordo con un esempio, criticarlo sul merito tecnico da solo ('Perché alla gente non piace goto 'non è un motivo tecnico).

Vediamo se possiamo parlarne come adulti.

Modifica

Questa domanda sembra terminata ora. Ha generato alcune risposte di alta qualità. Grazie a tutti,  specialmente quelli che hanno preso sul serio il mio piccolo esempio di loop. La maggior parte degli scettici erano preoccupati  dalla mancanza di portata del blocco. Come ha sottolineato @quinmars in un commento, puoi sempre mettere parentesi graffe attorno al corpo ad anello. Noto di passaggio che per (;;) e mentre (true) non ti danno le parentesi graffe gratuitamente (o ometterli può causare fastidiosi bug). Comunque, non sprecherò più della tua potenza cerebrale in questa sciocchezza: posso vivere con il innocuo e idiomatico per (;;) e mentre (vero) (altrettanto bene se voglio mantenere il mio lavoro).

Considerando le altre risposte, vedo che molte persone vedono goto come qualcosa che hai sempre riscrivere in un altro modo. Ovviamente puoi evitare un goto introducendo un loop,  una bandiera extra, una pila di nidificati se o altro, ma perché non considerare se goto è forse lo strumento migliore per il lavoro? Detto in altro modo, quanta bruttezza sono le persone disposte a sopportare per evitare di utilizzare una funzione di linguaggio integrata per lo scopo previsto? La mia opinione è quella anche l'aggiunta di una bandiera è un prezzo troppo alto da pagare. Mi piacciono le mie variabili per rappresentare le cose i domini problema o soluzione. 'Solo per evitare un goto ' non lo taglia.

Accetterò la prima risposta che ha dato il modello C per la ramificazione in un blocco di pulizia. IMO, questo è sicuramente il caso migliore per un goto di tutte le risposte postate se lo si misura dalle contorsioni che un odiatore deve attraversare per evitarlo.

È stato utile?

Soluzione

Ecco un trucco che ho sentito parlare di persone che usano. Non l'ho mai visto in natura però. E si applica solo a C perché C ++ ha RAII per farlo in modo più idiomatico.

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

    /* everything has succeeded */
    return;

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

Altri suggerimenti

Il classico bisogno di GOTO in C è il seguente

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

final:

Non esiste un modo semplice per uscire dai loop nidificati senza andare in goto.

Ecco il mio esempio non sciocco, (da Stevens APITUE) per le chiamate di sistema Unix che possono essere interrotte da un segnale.

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

        // handle real errors
    }

L'alternativa è un ciclo degenerato. Questa versione è in inglese "se la chiamata di sistema è stata interrotta da un segnale, riavviarla".

Se il dispositivo di Duff non ha bisogno di un goto, allora nemmeno tu! ;)

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);
    }
}

codice sopra da Wikipedia entry .

Knuth ha scritto un documento "Programmazione strutturata con istruzioni GOTO", puoi ottenerlo ad es. da qui . Lì troverai molti esempi.

Molto comune.

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;
}

L'unico caso che io abbia mai usato goto è per saltare in avanti, di solito fuori dai blocchi e mai nei blocchi. Questo evita l'abuso di do {} while (0) e di altri costrutti che aumentano il nesting, pur mantenendo un codice leggibile e strutturato.

Non ho nulla contro i goto in generale, ma posso pensare a diversi motivi per cui non vorresti usarli per un ciclo come hai menzionato:

  • Non limita l'ambito, quindi eventuali variabili temporanee utilizzate all'interno non verranno liberate fino a tardi.
  • Non limita l'ambito, quindi potrebbe portare a bug.
  • Non limita l'ambito quindi non è possibile riutilizzare gli stessi nomi di variabili in un secondo momento nel codice futuro nello stesso ambito.
  • Non limita l'ambito, quindi hai la possibilità di saltare una dichiarazione variabile.
  • Le persone non ci sono abituate e ciò renderà più difficile la lettura del tuo codice.
  • I loop nidificati di questo tipo possono portare al codice spaghetti, i loop normali non portano al codice spaghetti.

Un buon posto per usare un goto è in una procedura che può interrompersi in diversi punti, ognuno dei quali richiede vari livelli di pulizia. I gotofobi possono sempre sostituire le goto con codice strutturato e una serie di test, ma penso che questo sia più semplice perché elimina un eccessivo rientro:

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: lo snippet di codice pubblicato è equivalente al seguente:

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

            break;
        }
    }

Preferisco decisamente questo modulo.

Anche se ho imparato a odiare questo modello nel tempo, è integrato nella programmazione 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;
}

Ecco un esempio di un buon goto:

// No Code

Ho visto goto usato correttamente ma le situazioni sono normalmente brutte. È solo quando l'uso di goto è molto meno peggio dell'originale. @Johnathon Holland il problema è che la tua versione è meno chiara. le persone sembrano avere paura delle variabili locali:

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

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

E preferisco i loop come questo, ma alcune persone preferiscono mentre (true) .

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

La mia lamentela a riguardo è che si perde lo scoping a blocchi; qualsiasi variabile locale dichiarata tra i gotos rimane in vigore se il loop viene mai interrotto. (Forse stai supponendo che il ciclo funzioni per sempre; non penso che sia quello che stava chiedendo lo scrittore di domande originale.)

Il problema di scoping è più un problema con C ++, poiché alcuni oggetti potrebbero dipendere dal fatto che il loro dtor venga chiamato al momento opportuno.

Per me, il miglior motivo per usare goto è durante un processo di inizializzazione in più passaggi in cui è vitale che tutti gli INIT vengano esclusi se uno fallisce, alla:

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;

Non uso Goto's me stesso, tuttavia una volta ho lavorato con una persona che li avrebbe usati in casi specifici. Se ricordo bene, la sua logica era legata ai problemi di performance - aveva anche regole specifiche per come . Sempre nella stessa funzione e l'etichetta era sempre SOTTO l'istruzione 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:

Perché non fare il tuo esempio in questo modo:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top