GOTO считается безвредным при переходе на очистку в конце функции? - PullRequest
20 голосов
/ 18 апреля 2011

Заявление goto было подробно рассмотрено в нескольких обсуждениях SO (см. , и , ), и я, конечно, не хочу возобновлять эти горячие дебаты.

Вместо этого я бы хотел сосредоточиться на одном случае использования goto с и обсудить его значение и возможные альтернативы.

Рассмотрим следующий фрагмент кода, который часто встречается в (по крайней мере, в моих) автоматах:

while (state = next_state()) {
        switch (state) {
                case foo:
                        /* handle foo, and finally: */
                        if (error) goto cleanup;
                        break;
                case bar:
                        /* handle bar, and finally: */
                        if (error) goto cleanup;
                        break;
                /* ...other cases... */
        }
}

return ok;

cleanup:
/* do some cleanup, i.e. free() local heap requests, adjust global state, and then: */
return error;

Замена элементов очистки в отдельной функции только для того, чтобы сохранить goto, кажется неудобной. С другой стороны, нас подняли, чтобы осудить использование goto s везде, где это возможно.

Мой вопрос: мой пример кода считается хорошим стилем?
Если нет, есть ли доступные альтернативы?

Пожалуйста, придерживайтесь специального использования goto, описанного выше. Я не хочу углубляться в еще одно обсуждение общего использования goto.

Ответы [ 10 ]

11 голосов
/ 18 апреля 2011

Ваше использование goto в порядке.Это не нарушает 2 хороших способа использования goto.

  1. goto s ДОЛЖНЫ идти вниз (несколько строк) в источнике
  2. Самый внутренний блок goto labelsДОЛЖЕН содержать goto операторов
6 голосов
/ 16 мая 2011

Goto не требуется, если у вас есть switch. Использование switch и goto только добавляет сложность.

while (state) {
        switch (state) {
                case cleanup:
                        /* do some cleanup, i.e. free() local heap requests, adjust global state, and then: */
                        return error;
                case foo:
                        /* handle foo, and finally: */
                        if (error) { state = cleanup; continue; }
                        break;
                case bar:
                        /* handle bar, and finally: */
                        if (error) { state = cleanup; continue; }
                        break;
                /* ...other cases... */
        }
        state = next_state();
}

return ok;
6 голосов
/ 18 апреля 2011

Вместо того, чтобы извлекать логику очистки в свою собственную функцию и вызывать ее из разных мест, я хотел бы рассмотреть вопрос о выделении оператора switch в отдельную функцию и возврате из нее кода ошибки. В вашем цикле while вы можете проверить код возврата, выполнить очистку и вернуться, если необходимо.

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

4 голосов
/ 18 апреля 2011

Я видел, как goto использовался таким образом в ядре OpenBSD, в частности в драйверах устройств ATA ( один такой пример ), и я лично чувствую, что это хороший стиль, поскольку он помогает проиллюстрировать, что именно происходит и как код соответствует до соответствующего FSM. При попытке проверить функциональность FSM по спецификации, такое использование goto несколько улучшает ясность.

4 голосов
/ 18 апреля 2011

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

2 голосов
/ 01 августа 2014

Глядя на ответ Бена Фойгта, я получил альтернативный ответ:

while (state = next_state()) {
        switch (state) {
                case foo:
                        /* handle foo, and finally: */
                        /* error is set but not bothered with here */ 
                        break;
                case bar:
                        /* handle bar, and finally: */
                        /* error is set but not bothered with here */
                        break;
                /* ...other cases... */
        }

        if (error) {
                /* do some cleanup, i.e. free() local heap requests, */
                /* adjust global state, and then: */
                return error;
        }
}

return ok;

Недостатком этого является то, что вы должны помнить, что после обработки состояния оно может очиститься в случае ошибки. Похоже, структура if может представлять собой цепочку if-else для обработки различных типов ошибок.

Я не посещал формальные занятия по автоматам, но мне кажется, что код, который вы опубликовали, имеет такое же поведение.

2 голосов
/ 18 апреля 2011

Если весь ваш код инициализации сделан до входа в цикл while, то ваши gotos бесполезны, вы можете выполнить очистку при выходе из цикла.Если ваш конечный автомат предназначен для того, чтобы приводить данные в правильном порядке, то почему бы и нет, но поскольку у вас есть конечный автомат, почему бы не использовать его для очистки?все вместе и с простым кодом обработки ошибок, как обсуждено здесь .Но если у вас возникнут проблемы с настройкой конечного автомата, то я не вижу веской причины использовать их.ИМО, вопрос все еще слишком общий, полезен более практичный пример конечного автомата.

1 голос
/ 15 декабря 2014

Общий принцип, которому я хотел бы следовать, заключается в том, что по возможности следует пытаться писать код, чей поток и дизайн соответствуют задачной области («что нужно сделать программе»).Языки программирования включают управляющие структуры и другие функции, которые хорошо подходят для большинства, но не для всех проблемных областей.Такие структуры следует использовать, когда они соответствуют требованиям программы.В тех случаях, когда требования программы не очень соответствуют функциональным возможностям языка, я предпочитаю сосредоточиться на написании кода, который выражает то, что нужно программе, а не на перекодировке кода для соответствия шаблонам, которые в то время соответствуют требованиям других приложений., на самом деле не соответствуют требованиям к написанной программе.

В некоторых случаях, очень естественный способ преобразования конечных автоматов в код, в тех случаях, когда подпрограмма не может "выйти"«до тех пор, пока конечный автомат не достигнет какой-либо формы заключения, должен иметь метку goto, представляющую каждое состояние, и использовать операторы использования if и goto для переходов состояний.Если требуемые переходы состояний лучше подходят для других управляющих структур (например, циклов while), то использование таких циклов будет лучше, чем операторов goto, а использование операторов switch может привести к определенным видам "адаптаций" (например,гораздо проще выполнять процедуру, выполняющую переход состояния каждый раз, когда она выполняется, вместо того, чтобы требовать ее немедленного выполнения до завершения.С другой стороны, поскольку оператор switch на самом деле является замаскированным просто «goto», может быть, проще было бы просто напрямую использовать goto, чем использовать оператор switch для имитации.

1 голос
/ 17 мая 2011

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

1 голос
/ 18 апреля 2011

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

...