it-swarm.com.ru

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

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

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

infinite_loop:

    // code goes here

goto infinite_loop;

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

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

Правила:

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

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

Правка

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

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

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

79
fizzer

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

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

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

final:

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

85
Paul Nathan

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

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

        // handle real errors
    }

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

32
fizzer

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

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

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

14
Mitch Wheat

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

14
zvrba

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

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

12
ephemient

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

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

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

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

 if (! getDataFromFile ()) 
 goto closeFileAndQuit; 

 if (! allocateSomeResources) 
 goto freeResourcesAndQuit; 

 // Проделайте дополнительную работу здесь ....

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

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

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

            break;
        }
    }

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

7
Mitch Wheat

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

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

// No Code
5
FlySwat

Я видел, что 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
}
1
Charles Beattie

Моя претензия к этому заключается в том, что вы теряете область видимости; Любые локальные переменные, объявленные между 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;
0
Jim Nelson
#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);
}
0
StrifeSephiroth

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

0
user25306