Примеры хороших goto в C или C++ [закрыто]


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

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

infinite_loop:

    // code goes here

goto infinite_loop;

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

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

Правила:

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

Edit

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

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

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

16 79

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:

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

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

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

        // handle real errors
    }

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

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

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

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

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

Очень часто.

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

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

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

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

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: ваш опубликованный фрагмент кода эквивалентен следующему:

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

            break;
        }
    }

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

Хотя со временем я возненавидел этот паттерн, он встроен в Программирование 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;
}

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

// No Code

Я видел, что Гото используется правильно, но ситуации обычно уродливы. Это только тогда, когда использование goto само по себе намного хуже, чем оригинал. @Джонатан Голландии 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
}

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

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

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

Я сам не использую 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;
}