Примеры хороших gotos в C или С++

В этом потоке мы рассмотрим примеры хорошего использования goto в C или С++. Это вдохновило ответ, который люди голосовали, потому что они думали, что я шучу.

Сводка (ярлык изменен с оригинала, чтобы сделать еще четкость):

infinite_loop:

    // code goes here

goto infinite_loop;

Почему это лучше, чем альтернативы:

  • Это конкретный. goto - это языковой конструкции, которая безусловная ветвь. альтернативы зависят от использования структур поддерживающие условные ветки, с вырожденным всегда-истинным состояние.
  • Метка документирует намерение без дополнительных комментариев.
  • Читателю не нужно сканировать промежуточный код для ранних break s (хотя все еще возможно беспринципный хакер для имитации continue с ранним goto).

Правила:

  • Притворись, что недостойные выиграть. Он понимал, что вышеупомянутое не может использоваться в реальном коде, потому что это противоречит установленной идиоме.
  • Предположим, что мы все слышали о "Перейти считаться вредным" и знать что goto можно использовать для записи спагетти.
  • Если вы не согласны с примером, критиковать его по техническим заслугам один ("Потому что людям не нравится goto 'не является технической причиной).

Посмотрим, можем ли мы говорить об этом, как взрослые.

Edit

Этот вопрос кажется завершенным. Это вызвало некоторые высококачественные ответы. Спасибо всем,  особенно тех, кто серьезно относился к моему примеру с маленькой петлей. Большинство скептиков были обеспокоены  из-за отсутствия блока. Как отметил @quinmars в комментарии, вы всегда можете поместить фигурные скобки вокруг тело петли. Я отмечаю попутно, что for(;;) и while(true) не дают вам фигурные скобки бесплатно (и опускание их может вызвать неприятные ошибки). Во всяком случае, я больше не буду тратить вашей мозговой силы на эту мелочь - я могу жить с безвредными и идиоматическими for(;;) и while(true) (так же хорошо, если я хочу сохранить свою работу).

Учитывая другие ответы, я вижу, что многие люди рассматривают goto как нечто, что вы всегда придется переписывать по-другому. Конечно, вы можете избежать goto, введя цикл,  дополнительный флаг, стек вложенных if s или что-то еще, но почему бы не подумать, является ли goto возможно, лучший инструмент для работы? Иными словами, сколько уродства люди готовы выдержать, чтобы избежать использования встроенной языковой функции по назначению? Мое мнение таково: даже добавление флага слишком велико, чтобы заплатить. Мне нравятся мои переменные для представления вещей в области проблем или решений. "Единственно, чтобы избежать goto" не разрезает его.

Я приму первый ответ, который дал шаблон C для ветвления в блок очистки. ИМО, это делает самый сильный случай для goto всех опубликованных ответов, конечно если вы измеряете его с помощью искажений, которые должен пройти противник, чтобы избежать этого.

Ответ 1

Вот один трюк, который я слышал о людях, которые используют. Я никогда не видел его в дикой природе. И это применимо только к C, потому что С++ имеет RAII, чтобы сделать это более идиоматично.

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

    /* everything has succeeded */
    return;

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

Ответ 2

Классическая потребность в GOTO в C выглядит следующим образом

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

final:

Нет простого способа вырваться из вложенных циклов без goto.

Ответ 3

Вот мой не-глупый пример (от Stevens APITUE) для системных вызовов Unix, которые могут быть прерваны сигналом.

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

        // handle real errors
    }

Альтернативой является вырожденная петля. Эта версия читается как английский "если системный вызов был прерван сигналом, перезапустите его".

Ответ 4

Если устройству Даффа не нужен переход, то и вы не должны!;)

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

код выше из Википедии запись.

Ответ 5

Кнут написал статью "Структурированное программирование с операторами GOTO", вы можете получить ее, например. от здесь. Здесь вы найдете много примеров.

Ответ 6

У меня нет ничего против gotos вообще, но я могу думать о нескольких причинах, почему вы не хотели бы использовать их для цикла, как вы упомянули:

  • Он не ограничивает область, поэтому любые временные переменные, которые вы используете внутри, не будут освобождены до более позднего времени.
  • Он не ограничивает область, поэтому это может привести к ошибкам.
  • Он не ограничивает область, поэтому вы не можете повторно использовать одни и те же имена переменных позже в будущем коде в той же области.
  • Он не ограничивает область, поэтому у вас есть шанс пропустить объявление переменной.
  • Люди не привыкли к этому, и это сделает ваш код более трудным для чтения.
  • Вложенные петли этого типа могут привести к коду спагетти, нормальные петли не приведут к коду спагетти.

Ответ 7

Очень часто.

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

Единственный случай, когда я когда-либо использовал goto, - это прыгать вперед, обычно из блоков, и никогда не блокировать. Это позволяет избежать злоупотреблений do{}while(0) и других конструкций, которые увеличивают вложенность, сохраняя при этом читаемый структурированный код.

Ответ 8

Хорошее место для использования goto - это процедура, которая может прерываться в нескольких точках, каждая из которых требует различных уровней очистки. Gotophobes всегда может заменить gotos структурированным кодом и серией тестов, но я думаю, что это более просто, потому что он устраняет чрезмерный отступ:

if (!openDataFile())
  goto quit;

if (!getDataFromFile())
  goto closeFileAndQuit;

if (!allocateSomeResources)
  goto freeResourcesAndQuit;

// Do more work here....

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

Ответ 9

@fizzer.myopenid.com: ваш опубликованный фрагмент кода эквивалентен следующему:

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

            break;
        }
    }

Я определенно предпочитаю эту форму.

Ответ 10

Несмотря на то, что я ненавидел этот шаблон с течением времени, он был встроен в 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;
}

Ответ 11

Вот пример хорошего goto:

// No Code

Ответ 12

Я видел, что goto используется правильно, но ситуации нормальны, уродливые. Только когда использование goto само по себе намного хуже, чем оригинал. @Johnathon Holland the poblem - это версия, которая менее понятна. люди, похоже, боятся локальных переменных:

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

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

И я предпочитаю такие петли, но некоторые предпочитают while(true).

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

Ответ 13

Моя проблема в том, что вы теряете область обзора; любые локальные переменные, объявленные между gotos, остаются в силе, если цикл когда-либо вырвался из. (Возможно, вы предполагаете, что цикл работает навсегда, я не думаю, что то, что задавал автор оригинального вопроса).

Проблема определения области обзора скорее связана с С++, так как некоторые объекты могут зависеть от того, что их dtor вызывается в соответствующее время.

Для меня лучшая причина использования goto заключается в многоэтапном процессе инициализации, где жизненно важно, чтобы все inits были защищены от отказа, 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;

Ответ 14

Я не использую goto самостоятельно, однако я работал с человеком однажды, который будет использовать их в конкретных случаях. Если я правильно помню, его обоснование касалось проблем с производительностью - у него также были определенные правила. Всегда в одной и той же функции, и метка всегда была ниже. Операция goto.

Ответ 15

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

Ответ 16

@Greg:

Почему бы вам не сделать так:

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}