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

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

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

infinite_loop:

    // code goes here

goto infinite_loop;

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

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

Правила:

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

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

редактировать

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

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

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

16 ответов

Решение

Вот один трюк, который я слышал о людях, использующих. Я никогда не видел это в дикой природе, хотя. И это относится только к C, потому что 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;
}

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

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

final:

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

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

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

        // handle real errors
    }

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

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

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

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

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

Очень распространенный.

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) и другие конструкции, которые увеличивают вложение, сохраняя читаемый структурированный код.

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

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

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

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

            break;
        }
    }

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

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

if (! openDataFile ())
  перейти к выходу;

if (! getDataFromFile ())
  goto closeFileAndQuit;

if (! allocateSomeResources)
  goto freeResourcesAndQuit;

// Делай больше работы здесь....

freeResourcesAndQuit:
   // свободные ресурсы
closeFileAndQuit:
   // закрыть файл
уволиться:
   // уволиться!

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

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

// No Code

Я видел, что goto используется правильно, но ситуации обычно ужасны. Это только когда использование goto Сам по себе намного меньше, чем оригинал. @ Джонатон Холланд, проблема в том, что твоя версия менее ясна. люди, кажется, боятся локальных переменных:

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
}

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

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

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

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;

Я сам не пользуюсь goto, однако однажды я работал с человеком, который использовал их в определенных случаях. Если я правильно помню, его обоснование было связано с проблемами производительности - у него также были конкретные правила, как это сделать. Всегда в одной и той же функции, а метка всегда была НИЖЕ оператором 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:

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

void foo()
{
    if (doA())
    {    
        if (doB())
        {
          if (!doC())
          {
             UndoA();
             UndoB();
          }
        }
        else
        {
          UndoA();
        }
    }
    return;
}
Другие вопросы по тегам