ПЕРЕЙТИ или НЕ ПЕРЕЙТИ?

В настоящее время я работаю над проектом, в котором широко используются операторы goto. Основная цель операторов goto состоит в том, чтобы иметь в подпрограмме один раздел очистки, а не несколько операторов возврата. Как показано ниже:

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
     goto Exit;
   }

   // Lot of code...

Exit:
   if (p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

Это значительно упрощает процесс, поскольку мы можем отслеживать наш код очистки в одном разделе кода, то есть после метки выхода.

Тем не менее, я читал много мест, где использовать операторы goto - плохая практика.

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

Какой путь правильный?


Из комментария Скотта:

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

Но если мы используем goto только для перехода вперед и к одной метке, тогда все будет в порядке (?).

У вашего фрагмента кода нет возврата и нет gotos fyi. Это делает ваш первый абзац немного глупым.

Greg Rogers 18.12.2008 23:46

Разметка делала забавные вещи - теперь она исправлена

Eclipse 18.12.2008 23:53

Тема дает впечатление спорного вопроса. Хотя нет хороших противников ..

Filip Ekberg 19.12.2008 00:05

Мои мысли: Решите, какой язык вы используете. В C++ есть более совершенные инструменты для гарантии clenaup (RAII, как указано ниже), но в C нет, а в C очень часто используется goto описанным вами способом.

jalf 19.12.2008 00:39

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

Martin York 19.12.2008 00:54

Также обратите внимание, что тест (p == NULL) НИКОГДА не завершится ошибкой. 'p' всегда гарантированно будет действительным указателем после завершения new. Если новый не работает, он вызовет исключение (в это время произойдет утечка остальной части вашего кода).

Martin York 19.12.2008 00:58

@Martin York: RIAA не сделает вашу уборку, а только подаст на вас в суд. ;-)

John Zwinck 19.12.2008 02:44

Я молюсь собаке каждый день, чтобы исправить это ;-)

Martin York 19.12.2008 09:14

Гото даже не преподают в нашем колледже.

Calmarius 08.05.2012 12:27

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

ActiveTrayPrntrTagDataStrDrvr 01.03.2013 12:12

этот код ранит мою душу

rossb83 19.07.2018 20:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
62
11
79 351
35
Перейти к ответу Данный вопрос помечен как решенный

Ответы 35

В общем, вы должны разрабатывать свои программы так, чтобы ограничить потребность в gotos. Используйте объектно-ориентированные методы для «очистки» возвращаемых значений. Есть способы сделать это, не требующие использования gotos или усложнения кода. Есть случаи, когда gotos очень полезны (например, глубоко вложенные области видимости), но по возможности их следует избегать.

Глубоко вложенные области видимости могут быть реорганизованы в отдельную функцию, и тогда один оператор return легко заменяет там и ваш goto. :)

jalf 19.12.2008 00:43
Ответ принят как подходящий

Я не уверен, что вы имеете в виду под чистым кодом, но в C++ есть концепция под названием «получение ресурсов - это инициализация», и ваши деструкторы должны нести ответственность за очистку материала.

(Обратите внимание, что в C# и Java это обычно решается с помощью try / finally)

Для получения дополнительной информации посетите эту страницу: http://www.research.att.com/~bs/bs_faq2.html#finally

РЕДАКТИРОВАТЬ: Позвольте мне немного прояснить это.

Рассмотрим следующий код:

void MyMethod()
{
    MyClass *myInstance = new MyClass("myParameter");
    /* Your code here */
    delete myInstance;
}

Проблема: Что произойдет, если у вас есть несколько выходов из функции? Вы должны следить за каждым выходом и удалять свои объекты при всех возможных выходах! В противном случае у вас будут утечки памяти и зомби-ресурсы, верно?

Решение: вместо этого используйте ссылки на объекты, так как они очищаются автоматически, когда элемент управления покидает область видимости.

void MyMethod()
{
    MyClass myInstance("myParameter");
    /* Your code here */
    /* You don't need delete - myInstance will be destructed and deleted
     * automatically on function exit */
}

О да, и используйте std::unique_ptr или что-то подобное, потому что приведенный выше пример явно несовершенный.

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

anand 18.12.2008 23:47

ALien01: Множественные пути возврата и множественные, "goto end of function" в точности равны по сложности для отслеживания.

Brian 19.12.2008 00:48

Приведенный выше код находится на C, поэтому методы C++, похоже, не применяются

chmike 24.05.2009 14:31

Упс, проверил еще раз, и это действительно C++. Так что лучше использовать RIAA.

chmike 24.05.2009 14:34

Здесь нет никаких «объектных ссылок».

Lightness Races in Orbit 20.06.2013 03:06

Иногда, когда важна эффективность, мы не хотим использовать RAII, потому что это будет означать, например, ненужную копию списка указателей. Вместо этого мы просто хотим убедиться, что они либо возвращаются, либо удаляются в случае сбоя.

VinGarcia 20.07.2016 15:59

Использование goto для перехода в раздел очистки вызовет множество проблем.

Во-первых, разделы очистки подвержены проблемам. Они обладают низкой связностью (нет реальной роли, которую можно описать в терминах того, что программа пытается делать), высокой связностью (правильность очень сильно зависит от других разделов кода) и вовсе небезопасны в отношении исключений. Посмотрим, можно ли использовать деструкторы для очистки. Например, если int *p заменен на auto_ptr<int> p, то то, на что указывает p, будет автоматически освобождено.

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

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

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

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

anand 18.12.2008 23:51

Вы думаете, что несколько путей возврата - это проблема, а переходы - нет?

David Thornley 19.12.2008 00:23

Почему вы считаете проблемой несколько путей возврата? И если вы не объявите возвращаемое значение заранее, вы никогда не забудете его установить. (вместо bRetVal = FALSE в верхней части функции просто верните напрямую true / false.

jalf 19.12.2008 01:10

Наверное, не хорошая идея.

я тоже .. Я распечатал это и разместил в моем кубе. Бойтесь велоцираптора.

FryGuy 19.12.2008 00:18

Все вышеперечисленное верно, вы также можете захотеть посмотреть, сможете ли вы уменьшить сложность своего кода и уменьшить потребность в goto, уменьшив количество кода, которое находится в разделе, помеченном как «много кода». в вашем примере. Дополнительно delete 0 является допустимым оператором C++

Ваш код крайне неидиоматичен, и вы никогда не должны его писать. Вы в основном эмулируете C в C++. Но другие отметили это и указали на RAII в качестве альтернативы.

Однако ваш код не будет работать, как вы ожидаете, потому что это:

p = new int;
if (p==NULL) { … }

не Когда-либо оценивает true (кроме случаев, когда вы перегрузили operator new странным образом). Если operator new не может выделить достаточно памяти, он выдает исключение, никогда, Когда-либо возвращает 0, по крайней мере, не с этим набором параметров; существует специальная перегрузка с новым размещением, которая принимает экземпляр типа std::nothrow и действительно возвращает 0 вместо того, чтобы генерировать исключение. Но эта версия редко используется в обычном коде. Некоторые низкоуровневые коды или приложения для встраиваемых устройств могут извлечь выгоду из этого в контекстах, где обработка исключений обходится слишком дорого.

Нечто подобное верно и для вашего блока delete, как сказал Харальд: if (p) не нужен перед delete p.

Кроме того, я не уверен, что ваш пример был выбран намеренно, потому что этот код можно переписать следующим образом:

bool foo() // prefer native types to BOOL, if possible
{
    bool ret = false;
    int i;
    // Lots of code.
    return ret;
}

Новый оператор вернет NULL. Оформить заказ по ссылке: msdn.microsoft.com/en-us/library/kftdy56f(VS.71).aspx

anand 19.12.2008 00:03

Нет, это не будет. В этой статье используется другая перегрузка new, а именно оператор nothrow new. Об этом я говорил в своем ответе выше. Я постараюсь уточнить это дальше.

Konrad Rudolph 19.12.2008 00:10

на самом деле код эмулирует Fortran, а не C. Нет причин использовать GOTO в C eiter.

andreas buykx 22.12.2008 23:30

@andreas: По общему признанию, я не программист на C. Как вы обрабатываете ошибки и сложную логику очистки в C, не прибегая к goto или запутанным вложенным блокам if?

Konrad Rudolph 23.12.2008 02:55

... или если вы странным образом перегрузили operator ==.

Ferruccio 10.07.2012 16:25

@Ferruccio Вы не можете перегружать операторы для встроенных типов (operator new является исключением, потому что это функция, несмотря на свое имя, в отличие от других операторов).

Konrad Rudolph 10.07.2012 16:27

@KonradRudolph - правда, но p может быть определяемым пользователем типом, который неявно конвертируется из int*.

Ferruccio 10.07.2012 16:34

@Ferruccio Он объявлен как int* в коде OP… и это был код, о котором я имел в виду.

Konrad Rudolph 10.07.2012 16:41

Попробуйте так:

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p = NULL;

   p = new int;
   if (p == NULL)
   {
     cout<<" OOM \n";
   }
   else
   {
       // Lot of code...
   }

   if (p)
   {
     delete p;
     p = NULL;
   }
   return bRetVal;
}

В разделе «Много кода» «Много кода» является хорошим показателем того, что вам, вероятно, следует преобразовать этот раздел в один или несколько методов или функций.

Это страдает теми же проблемами, на которые уже указывалось. Не пишите такой код на C++. Даже низкоуровневый код.

Konrad Rudolph 19.12.2008 00:00

@Konrad: Я понимаю, что вы не согласны с различными тестами на p, но я думаю, что это побочная проблема. Предложение Мэтью - AFAIK классический «правильный» метод избежания GOTO - вместо этого используйте реальную логику ветвления.

Dave Costa 19.12.2008 00:04

Я не согласен с @Konrad. Это может быть не из книги, он может пропустить, что delete NULL является допустимым, но OTOH плакаты ошибались в отношении исключений при ошибке OOM (подсказка: set_new_handler ()). И ни в коем случае это пост -1, это смешно, так что я буду поддерживать. И замечание о рефакторинге тоже очень верно.

mstrobl 19.12.2008 00:09

Конрад прав, здесь упускается принципиальный момент. К черту тесты на p, они не важны. Важно то, что вы используете простое, подверженное ошибкам ветвление, когда C++ предоставляет вам гораздо лучшие инструменты для обработки очистки, такие как RAII.

jalf 19.12.2008 00:44

@mstrobl: Нулевой тест здесь был лишь крошечной частью моей критики. Весь код в его нынешнем виде является антипаттерном C++. Джалф меня понял. Кроме того, замечание «рефакторинга» может быть справедливым, но я считаю его переоцененным. Некоторые функции делать на практике затягиваются, и вы ничего не можете сделать, чтобы это предотвратить.

Konrad Rudolph 19.12.2008 01:26

Вы ничего не можете сделать с предотвращать, но вы также можете исправить обычно. В любом случае, это то, над чем стоит серьезно подумать, прежде чем вы начнете добавлять GOTO и код очистки.

Adam Jaskiewicz 19.12.2008 05:51

Насколько я понимаю, решение Mattew - хорошее решение C (заменить new / delete на malloc / free). Но это ПЛОХОЕ решение C++: отсутствие RAII (ни стек, ни интеллектуальный указатель), незнание того факта, что должен генерироваться сбой new, и т. д. Перечитывая вопрос, я вижу как использование слова «new», так и тега «C++» ...

paercebal 20.12.2008 19:38

Вся цель идиомы «каждая функция имеет единственную точку выхода» в C заключалась в том, чтобы собрать весь материал для очистки в одном месте. Если вы используете деструкторы C++ для обработки очистки, в этом больше нет необходимости - очистка будет выполняться независимо от того, сколько точек выхода имеет функция. Так что в правильно спроектированном коде C++ в подобных вещах больше нет необходимости.

Использование «GOTO» изменит «логику» программы и то, как вы ее вводите или как вы представляете, как она будет работать.

Мне всегда удавалось избегать GOTO-команд, поэтому угадайте, когда вы думаете, что они могут вам понадобиться, все, что вам может понадобиться, - это переработать дизайн.

Однако, если мы посмотрим на это на уровне Assmebly, использование «прыжка» похоже на использование GOTO, которое используется все время, НО в Assembly вы можете очистить, что, как вы знаете, у вас есть в стеке, и другие регистры перед вами передай.

Итак, при использовании GOTO, я бы удостоверился, что программное обеспечение будет «появляться», как предполагают со-кодировщики, GOTO окажет «плохое» влияние на ваше программное обеспечение imho.

Так что это скорее объяснение того, почему не использовать GOTO, а не решение для замены, потому что это ОЧЕНЬ зависит от того, как все остальное построено.

Мне никогда не приходилось использовать goto в C++. Всегда. КОГДА-ЛИБО. Если есть ситуация, ее следует использовать, это невероятно редко. Если вы действительно думаете о том, чтобы сделать goto стандартной частью своей логики, что-то пошло не так.

Короткий и простой ответ, но я думаю, что это решает его. goto находятся в языке по какой-то причине, но если вы действительно подумаете об их использовании, то вероятность того, что вы делаете это неправильно, составляет 99,9%. Не пишите свой код так, как будто он попадает в случай 0,01%, потому что, вероятно, это не так.

jalf 19.12.2008 00:42

Иногда вам нужен goto, чтобы выйти из вложенных циклов.

rlbond 27.04.2009 21:53

@rlbond: Хотя это часто повторяется, так же часто оказывается, что это ложь. (Это C++ с тегами. В C++ есть встраивание. Так что вы всегда можете использовать return вместо goto.)

sbi 31.10.2009 02:05

@ribond: "хочу"! = "нужно". Был там, сделал это - и, как показывает опыт, каждый goto, кажется, увеличивает затраты на обслуживание кода на 30%. Да-да, ты понимает код сейчас. Это ничего не говорит о программисте, который будет его модифицировать через два года.

Piskvor left the building 17.06.2010 11:58

Кстати, проверьте libpng / freetype / libjpeg / vorbis, как они ИСПОЛЬЗУЮТ goto там, где производительность критична! goto МОЖЕТ НЕМНОГО увеличить производительность. Также, пожалуйста, помните, что использование gotos в коде ШИФРОВАНИЯ ... НЕОБХОДИМО, если вы хотите обмануть хакеров / реверс-инженеров ЕЩЕ БОЛЬШЕ. Так что просто убей эту готофобию!

Петър Петров 11.06.2014 14:55

@ ПетърПетров, зачем нужно скрывать код шифрования от реверс-инжиниринга? Чтобы быть безопасным, полагайтесь на протокол, а не на безопасность через безвестность. Если вам действительно необходимо встроить секрет в клиентский код, используйте подходящий инструмент для обфускации.

RJFalconer 19.06.2014 16:46

... и инструменты обфускации часто преобразуют пути кода в тарабарщину с залитой скачком ASM, и goto в любом случае ничем не отличается от перехода на сборку :)

Петър Петров 23.06.2014 12:46

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

Zachary Kraus 19.09.2014 19:24

Нет лучшего решения для разрыва вложенных циклов, чем goto. Период. Да, ты можешь вернуться. Да, вы можете использовать непонятные bool. Да, можно даже злоупотреблять try / catch ржу не могу. Но все это хуже и либо делает код менее читаемым, либо ухудшает производительность. И если вы действительно находите двойной цикл с меткой выхода из цикла сразу после него слишком запутанным, вам лучше найти другую работу.

user2328447 12.10.2014 02:19

@ user2328447 Есть наверняка лучшие способы выхода из петель, чем goto, а как насчет return, continue или break для разумного управления петлей? Если вам действительно нужен более тонкий контроль, вы должны серьезно задуматься, как ваш код стал таким сложным, и серьезно подумать о том, чтобы разбить его на более тонкие компоненты для ясности!

Troyseph 20.10.2015 16:56

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

Jon Story 22.10.2015 14:31
Если вы действительно думаете о том, чтобы сделать goto стандартной частью своей логики, что-то пошло не так.: неверно.
René Nyffenegger 16.05.2016 14:01

Тройной цикл с непонятными bools для выхода И более неясными условиями окончания ?? Нет, спасибо. goto Break в данном случае.

Петър Петров 25.01.2017 17:29

Использование меток GOTO в C++ - плохой способ программирования, вы можете уменьшить потребность, выполнив ОО программирование (деконструкторы!) И попытавшись держать процедуры как можно меньше, насколько это возможно.

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

Вашу процедуру можно было бы просто написать так:

bool foo()
{
    bool bRetVal = false;
    int p = 0;

    // Calls to various methods that do algorithms on the p integer
    // and give a return value back to this procedure.

    return bRetVal;
}

Вы должны поместить блок try catch в основную программу, обрабатывающую проблемы с нехваткой памяти, который информирует пользователя о нехватка памяти, что бывает очень редко ... (разве сама ОС не сообщает об этом?)

Также обратите внимание, что не всегда есть необходимость использовать указатель, они полезны только для динамических вещей. (Создание чего-то одного внутри метода, не зависящего от ввода откуда-либо, на самом деле не динамично)

Сделайте p auto_ptr <int> или shared_ptr <int>, и вам не нужно беспокоиться о том, удаляет ли p; когда-либо выполняется.

David Thornley 19.12.2008 00:09

Перепишите ответ, и, кроме того, я (пока) не бустер, так как нам нужно использовать метод удаления в университете. Профилировщики кода могут помочь вам, когда вы забудете использовать удаление где-то в коде.

Tamara Wijsman 19.12.2008 00:14

Значит, плохой университет. auto_ptr находится в стандартной библиотеке, а shared_ptr будет в следующем году. Университеты не должны учить вас использовать удаление, это метод более 10 лет назад. (Конечно, показывать удаление при объяснении того, как работает RAII, - это нормально)

MSalters 19.12.2008 15:02

В общем, и на первый взгляд, в вашем подходе нет ничего плохого, при условии, что у вас есть только одна метка и что переходы всегда идут вперед. Например, такой код:

int foo()
{
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        delete pWhatEver;
        return 1;
    }
    else
    {
        delete pWhatEver;
        return 5;
    }
}

И этот код:

int foo()
{
    int ret;
    int *pWhatEver = ...;
    if (something(pWhatEver))
    { 
        ret = 1;
        goto exit;
    }
    else
    {
        ret = 1;
        goto exit;
    }
exit:
    delete pWhatEver;
    return ret;
}

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

Однако во многих случаях шаблон RAII (получение ресурсов - это инициализация) может сделать код намного чище и удобнее в обслуживании. Например, такой код:

int foo()
{
    Auto<int> pWhatEver = ...;

    if (something(pWhatEver))
    {
        return 1;
    }
    else
    {
        return 5;
    }
}

короче, легче читается и проще в обслуживании, чем оба предыдущих примера.

Итак, я бы порекомендовал использовать подход RAII, если вы можете.

Очень хорошо, если в вашем коде не используются исключения. К сожалению, в C++ есть исключения, поэтому нет. Чтобы сделать исключение кода безопасным, вы должны использовать RIAA.

Martin York 19.12.2008 01:10

А еще лучше просто использовать int whatEver = ...;. Не создавайте указатели, если вам не нужна семантика указателей. Это не Java.

David Stone 05.05.2012 19:57

RIAA предотвращает пиратство

paulm 29.06.2016 16:41

Поскольку это классическая тема, я отвечу Заявление о переходе считается вредным Дейкстры (изначально опубликовано в ACM).

Также прочтите «Структурированное программирование с операторами goto» Дональда Кнута, pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf. Использование goto для обработки ошибок - вполне допустимая стратегия в C и, как правило, лучший подход. Он широко используется в ядре Linux. С другой стороны, для C++ вполне могут быть другие лучшие подходы, что делает goto не лучшим выбором, мои знания C++ слишком слабы, чтобы что-то сказать по этому поводу.

hlovdal 11.05.2009 22:06

Все предыдущие комментарии - веские причины не использовать goto.

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

Из всех предыдущих комментариев:

  1. goto очень-очень плохо
  2. Это затрудняет чтение и понимание кода.
  3. Это может вызвать известную проблему «спагетти-код».
  4. Все согласны с тем, что этого нельзя делать.

Но я использую в следующем сценарии

  1. Используется для перехода вперед и только к одному лейблу.
  2. Раздел goto используется для очистки кода и установки возвращаемого значения. Если я не использую goto, мне нужно создать класс для каждого типа данных. Как будто мне нужно обернуть int * в класс.
  3. Это соблюдается во всем проекте.

Я согласен с тем, что это плохо, но все же при правильном соблюдении это значительно упрощает жизнь.

int * уже существует. Он называется std :: auto_ptr или boost :: shared_ptr. Но вы говорите: «Это не имеет значения, потому что весь проект уже использует gotos», так что ... почему вы вообще спросили? Еще раз выберите язык. C использует goto, C++ - нет.

jalf 19.12.2008 00:48

std :: auto_ptr <> - ваш друг. Это также безопасно в исключительных случаях. Ни один из ваших кодов не работает.

Martin York 19.12.2008 01:00

К сожалению, часто проще не приравнивать к исправлению.

Gene Roberts 19.12.2008 01:24

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

paercebal 20.12.2008 20:00

Я не собираюсь говорить, что goto всегда плох, но вы наверняка его используете. Подобные «разделы очистки» были довольно распространены в начале 1990-х годов, но использовать их для нового кода - чистое зло.

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

Mike Dunlavey 22.12.2008 23:48

«Чистое зло» - одна из самых распространенных фраз, которые можно услышать в любом обсуждении программирования. Остальные - «ересь», «богохульство», «грех» ...

Nemanja Trifunovic 25.12.2008 05:04

По сути, есть два момента, которые люди делают в отношении gotos и вашего кода:

  1. Гото плохой. Очень редко можно встретить место, где вам нужны gotos, но я бы не советовал удалять его полностью. Хотя C++ имеет достаточно умный поток управления, чтобы сделать goto редко подходящим.

  2. Ваш механизм очистки неправильный: Этот момент гораздо важнее. В языке C использование управления памятью по своему усмотрению не только нормально, но и зачастую является лучшим способом решения задач. В C++ ваша цель должна состоять в том, чтобы как можно больше избегать управления памятью. Вам следует по возможности избегать управления памятью. Пусть компилятор сделает это за вас. Вместо использования new просто объявите переменные. Единственный случай, когда вам действительно понадобится управление памятью, - это когда вы заранее не знаете размер ваших данных. Даже в этом случае вам следует попытаться использовать вместо этого просто некоторые из коллекций STL.

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

Ваш ответ о том, что ваш способ ведения дел намного проще, в конечном итоге неверен. Во-первых, как только вы прочувствуете C++, создание таких конструкторов станет второстепенным. Лично я считаю, что использовать конструкторы проще, чем использовать код очистки, поскольку мне не нужно уделять особое внимание, чтобы убедиться, что я освобождаю память правильно. Вместо этого я могу просто позволить объекту покинуть область видимости, и язык сделает это за меня. Кроме того, поддерживать их НАМНОГО проще, чем поддерживать раздел очистки, и они гораздо менее подвержены проблемам.

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

Самый простой способ избежать того, что вы здесь делаете, - поместить всю эту очистку в какую-то простую структуру и создать ее экземпляр. Например вместо:

void MyClass::myFunction()
{
   A* a = new A;
   B* b = new B;
   C* c = new C;
   StartSomeBackgroundTask();
   MaybeBeginAnUndoBlockToo();

   if ( ... )
   {
     goto Exit;
   }

   if ( ... ) { .. }
   else
   {
      ... // what happens if this throws an exception??? too bad...
      goto Exit;
   }

Exit:
  delete a;
  delete b;
  delete c;
  StopMyBackgroundTask();
  EndMyUndoBlock();
}

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

struct MyFunctionResourceGuard
{
  MyFunctionResourceGuard( MyClass& owner ) 
  : m_owner( owner )
  , _a( new A )
  , _b( new B )
  , _c( new C )
  {
      m_owner.StartSomeBackgroundTask();
      m_owner.MaybeBeginAnUndoBlockToo();
  }

  ~MyFunctionResourceGuard()
  {
     m_owner.StopMyBackgroundTask();
     m_owner.EndMyUndoBlock();
  }

  std::auto_ptr<A> _a;
  std::auto_ptr<B> _b;
  std::auto_ptr<C> _c;

};

void MyClass::myFunction()
{
   MyFunctionResourceGuard guard( *this );

   if ( ... )
   {
     return;
   }

   if ( ... ) { .. }
   else
   {
      ...
   }
}

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

Вместо этого, как должен выглядеть ваш код:

bool foo() //lowercase bool is a built-in C++ type. Use it if you're writing C++.
{
  try {
    std::unique_ptr<int> p(new int);
    // lots of code, and just return true or false directly when you're done
  }
  catch (std::bad_alloc){ // new throws an exception on OOM, it doesn't return NULL
    cout<<" OOM \n";
    return false;
  }
}

Ну, он короче и, насколько я понимаю, более правильный (правильно обрабатывает случай OOM), и, что наиболее важно, мне не нужно было писать какой-либо код очистки или делать что-то особенное, чтобы «убедиться, что мое возвращаемое значение инициализировано ".

Одна проблема с вашим кодом, которую я действительно заметил, когда писал это, - это «какое, черт возьми, значение bRetVal на данный момент?». Я не знаю, потому что выше он был объявлен waaaaay, и когда последний раз он был назначен? В какой-то момент выше этого. Мне нужно прочитать всю функцию, чтобы убедиться, что я понимаю, что будет возвращено.

И как мне убедить себя, что память освобождается?

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

Очень хрупкий код, потому что каждый, когда ресурс должен быть очищен, вам нужно помнить, чтобы дублировать ваш код очистки. Почему бы не написать его один раз в том виде, который нужно очистить? А затем полагаться на его автоматическое выполнение каждый раз, когда нам это нужно?

Ваш пример не безопасен.

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

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

Вам нужно изучить интеллектуальные указатели. В приведенной выше ситуации вы можете просто использовать std::auto_ptr<>.

Также обратите внимание, что в коде C++ нет необходимости проверять, является ли указатель NULL (обычно потому, что у вас никогда нет указателей RAW), но потому что new не вернет NULL (он выдает).

Также в C++, в отличие от (C), часто можно увидеть ранние возвраты в коде. Это связано с тем, что RAII выполнит очистку автоматически, в то время как в коде C вам нужно убедиться, что вы добавили специальный код очистки в конце функции (немного похожий на ваш код).

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

Mark Walsh 15.05.2018 17:37

@MarkWalsh Дело в том, что new никогда не возвращает nullptr. Поэтому нет необходимости проверять результат операции new, чтобы узнать, действительно ли она вернула указатель. Если вы дойдете до проверки, то это никогда не будет nullptr.

Martin York 15.05.2018 20:28

Возможно, я что-то пропустил: вы переходите к метке Exit, если P имеет значение NULL, затем проверяете, не является ли он нулевым (а это не так), чтобы узнать, нужно ли вам его удалить (что не обязательно, потому что он никогда не был выделен в первое место).

If / goto не будет, и ему не нужно удалять p. Замена goto на return false будет иметь тот же эффект (и тогда вы можете удалить метку Exit).

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

Функции (A вызывает A '), Try / Catches и setjmp / longjmps - все это более хорошие способы избежать сложной синтаксической проблемы.

Павел.

Игнорируя тот факт, что new никогда не вернет NULL, возьмите свой код:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p=NULL;

     p = new int;

     if (p==NULL)
     {
        cout<<" OOM \n";
        goto Exit;
     }

     // Lot of code...

  Exit:
     if (p)
     {
        delete p;
        p= NULL;
     }

     return bRetVal;
  }

и напишите это так:

  BOOL foo()
  {
     BOOL bRetVal = FALSE;

     int *p = new int;

     if (p!=NULL)
     {
        // Lot of code...

        delete p;
     }
     else
     {
        cout<<" OOM \n";
     }

     return bRetVal;
  }

Многие люди сходят с ума от gotos - это зло; они не. Тем не менее, он вам никогда не понадобится; почти всегда есть лучший способ.

Когда я обнаруживаю, что мне «нужен» goto для выполнения такого рода действий, я почти всегда обнаруживаю, что мой код слишком сложен и может быть легко разбит на несколько вызовов методов, которые легче читать и с которыми легче работать. Ваш код вызова может делать что-то вроде:

// Setup
if (
     methodA() &&
     methodB() &&
     methodC()
 )
 // Cleanup

Не то чтобы это было идеально, но за ним гораздо легче следить, поскольку все ваши методы будут названы, чтобы четко указывать, в чем может быть проблема.

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

Это плохая привычка программирования, нет необходимости использовать их, так как это только заставит вас писать более длинные методы. Вы можете заменить goto в его примере на: if (p) delete p; Зачем добавлять больше линий, чтобы достичь того же?

Tamara Wijsman 19.12.2008 04:35

именно то, что я сказал, спасибо за подкрепление.

Bill K 20.12.2008 00:34

В этом коде есть множество проблем, на большинство из которых уже указывалось, например:

  • Функция слишком длинная; может помочь рефакторинг некоторого кода в отдельные функции.

  • Использование указателей, когда обычные экземпляры, вероятно, будут работать нормально.

  • Не использовать преимущества типов STL, таких как auto_ptr

  • Неправильная проверка на ошибки, а не отлов исключений. (Я бы сказал, что проверка OOM бессмысленна на подавляющем большинстве платформ, поскольку, если у вас заканчивается память, у вас возникают большие проблемы, чем может исправить ваше программное обеспечение, если только вы не пишете саму ОС)

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

Вы должны прочитать это резюме из списков рассылки ядра Linux (обращая особое внимание на ответы Линуса Торвальдса), прежде чем формировать политику для goto:

http://kerneltrap.org/node/553/2131

Но с другой стороны, мнения Линуса часто сами по себе ошибочны. В конце концов, он (со-) изобрел войну пламени.

Konrad Rudolph 19.12.2008 11:32

Кроме того, C немного отличается от C++. Я вижу, что goto хорош, умеренно, если используется очень осторожно, в некоторых редких случаях, если вы пишете на чистом C, но если в вашем распоряжении есть инструменты C++, вам не следует писать с идиомами C.

Adam Jaskiewicz 19.12.2008 18:38

Хотя Линус - сертифицированный псих, он не ошибается в отношении операторов goto. При правильном использовании (как и во всем) они прекрасны, а иногда и предпочтительнее альтернативы.

Harvey 22.12.2008 21:48

Goto обеспечивает лучший не повторяйся (DRY), когда "конечная логика" является общей для некоторых, но не для всех случаев. Особенно в операторе switch я часто использую goto, когда некоторые из ветвей switch имеют конечную общность.

switch(){
   case a:  ... goto L_abTail;
   case b: ... goto L_abTail;
L_abTail: <commmon stuff>
    break://end of case b
case c:
.....
}//switch

Вы, наверное, заметили, что введения дополнительных фигурных скобок достаточно, чтобы удовлетворить компилятор, когда вам нужно такое слияние хвоста в середине подпрограммы. Другими словами, вам не нужно объявлять все наверху; это действительно плохая читаемость.

...
   goto L_skipMiddle;
{
    int declInMiddleVar = 0;
    ....
}
L_skipMiddle: ;

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

Кроме того, не повторяйтесь, присвоение имен меток такой конечной логике, кажется, даже помогает моему разуму сохранять ясность, выбирая красивые имена меток. Без значимого ярлыка ваши комментарии могут в конечном итоге сказать то же самое.

Конечно, если вы на самом деле выделяете ресурсы, тогда, если auto-ptr не подходит, вам действительно нужно использовать try-catch, но хвостовое слияние-не-повторять-себя происходит довольно часто, когда безопасность исключений Не ошибка.

Таким образом, хотя goto можно использовать для кодирования структур, похожих на спагетти, в случае хвостовой последовательности, которая является общей для некоторых, но не для всех случаев, goto УЛУЧШАЕТ читаемость кода и даже ремонтопригодность. если в противном случае вы будете копировать / вставлять что-то, чтобы намного позже кто-то мог обновить одно, а не другое. Так что это еще один случай, когда фанатичное отношение к какой-либо догме может быть контрпродуктивным.

Несколько лет назад я придумал псевдо-идиому, которая избегает goto и чем-то похожа на обработку исключений в C. Вероятно, она уже была изобретена кем-то другим, так что я думаю, что "открыл ее независимо" :)

BOOL foo()
{
   BOOL bRetVal = FALSE;
   int *p=NULL;

   do
   {
       p = new int;
       if (p==NULL)
       {
          cout<<" OOM \n";
          break;
       }

       // Lot of code...

       bRetVal = TRUE;

    } while (false);

   if (p)
   {
     delete p;
     p= NULL;
   }

   return bRetVal;
}

Код, который вы нам даете, - это (почти) код C, написанный внутри файла C++. Используемая вами очистка памяти подойдет для программы на C, не использующей код / ​​библиотеки C++.

В C++ ваш код просто небезопасен и ненадежен. В C++ тип управления, о котором вы просите, реализован иначе. Используйте конструкторы / деструкторы. Используйте умные указатели. Используйте стек. Одним словом, используйте RAII.

Ваш код может (т.е. на C++ ДОЛЖЕН) быть записан как:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<int> p = new int;

   // Lot of code...

   return bRetVal ;
}

(Обратите внимание, что создание int несколько глупо в реальном коде, но вы можете заменить int любым типом объекта, и тогда это будет иметь больше смысла). Представим, что у нас есть объект типа T (T может быть int, некоторым классом C++ и т. д.). Тогда код становится:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   std::auto_ptr<T> p = new T;

   // Lot of code...

   return bRetVal ;
}

Или даже лучше, используя стек:

BOOL foo()
{
   BOOL bRetVal = FALSE;

   T p ;

   // Lot of code...

   return bRetVal;
}

В любом случае, любой из приведенных выше примеров намного легче читать и надежнее, чем ваш пример.

RAII имеет множество аспектов (например, использование интеллектуальных указателей, стека, использование векторов вместо массивов переменной длины и т. д.), Но в целом речь идет о написании как можно меньше кода, позволяя компилятору очистить материал в нужный момент.

Alien01 wrote: Currently I am working on a project where goto statements are heavily used. The main purpose of goto statements is to have one cleanup section in routine rather than multiple return statements.

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

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

class auxNullPtrException : public std::exception {
    public:
        auxNullPtrException::auxNullPtrException()
            : std::exception( " OOM \n") {}
    };

    BOOL foo()
    {
        BOOL bRetVal = FALSE;
        try {
            int *p = NULL;
            p = new int;
            if (p == NULL)
            {
                throw auxNullPtrException();
            }
            // Lot of code...
         }
         catch(auxNullPtrException & _auxNullPtrException)
         {
             std::cerr<<_auxNullPtrException.what();
             if (p)
             {
                 delete p;
                 p = NULL;
             }
         }
         return bRetVal;
    }

Используемый в ядре Linux, goto, используемый для очистки, хорошо работает, когда одна функция должна выполнить 2 или более шагов, которые, возможно, потребуется отменить. Шаги не обязательно должны быть выделением памяти. Это может быть изменение конфигурации фрагмента кода или регистра набора микросхем ввода-вывода. Goto могут понадобиться только в небольшом количестве случаев, но часто при правильном использовании они могут быть решением Лучший. Они не злые. Они инструмент.

Вместо...

do_step1;
if (failed)
{
  undo_step1;
  return failure;
}

do_step2;
if (failed)
{
  undo_step2;
  undo_step1;
  return failure;
}

do_step3;
if (failed)
{
  undo_step3;
  undo_step2;
  undo_step1;
  return failure;
}

return success;

вы можете сделать то же самое с такими операторами goto:

do_step1;
if (failed) goto unwind_step1;

do_step2;
if (failed) goto unwind_step2;

do_step3;
if (failed) goto unwind_step3;

return success;

unwind_step3:
  undo_step3;

unwind_step2:
  undo_step2;

unwind_step1:
  undo_step1;

return failure;

Должно быть ясно, что в этих двух примерах один предпочтительнее другого. Что касается группы RAII ... В этом подходе нет ничего плохого, если они могут гарантировать, что раскрутка всегда будет происходить в строго обратном порядке: 3, 2, 1. И, наконец, некоторые люди не используют исключения в своем коде. и проинструктируйте компиляторы отключить их. Таким образом, не весь код должен быть безопасным в отношении исключений.

Не уверен, почему этот ответ был отклонен. Полагаю, фанатик против гото?

Harvey 15.04.2009 02:13

Может быть, потому что ваш код - это чистый код на C, а вопрос был помечен как C++? В C++ код очистки должен выполняться в окнах локальных автоматических объектов.

sbi 31.10.2009 02:15

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

Harvey 16.02.2010 01:49

А во Embedded даже вызов функции с ее Stack Unwinding будет содержать больше операций, чем инструкция перехода goto / asm к процедуре очистки. Да, и, конечно, принудительное встраивание.

Петър Петров 25.01.2017 17:34

@ ПетърПетров И любой достойный компилятор оптимизирует RAII скачкообразно. Так что разницы в производительности не будет.

dvr33 16.02.2020 20:24

Оборотная сторона GOTO довольно хорошо обсуждается. Я бы просто добавил, что 1) иногда вам нужно их использовать и нужно знать, как минимизировать проблемы, и 2) некоторые общепринятые методы программирования скрыты от GOTO, так что будьте осторожны.

1) Когда вам нужно использовать GOTO, например, в ASM или в файлах .bat, думайте, как компилятор. Если вы хотите кодировать

 if (some_test){
  ... the body ...
}

делать то, что делает компилятор. Создайте метку, цель которой - пропустить тело, а не делать то, что следует за ним. т.е.

 if (not some_test) GOTO label_at_end_of_body
  ... the body ...
label_at_end_of_body:

Нет

 if (not some_test) GOTO the_label_named_for_whatever_gets_done_next
  ... the body ...

the_label_named_for_whatever_gets_done_next:

Другими словами, цель метки - не для чего-то делать, а для чего-то пропустить.

2) То, что я называю GOTO-in-disguise, - это все, что можно превратить в код GOTO + LABELS, просто определив пару макросов. Примером является метод реализации конечных автоматов с помощью переменной состояния и оператора while-switch.

 while (not_done){
    switch(state){
        case S1:
            ... do stuff 1 ...
            state = S2;
            break;
        case S2:
            ... do stuff 2 ...
            state = S1;
            break;
        .........
    }
}

может превратиться в:

 while (not_done){
    switch(state){
        LABEL(S1):
            ... do stuff 1 ...
            GOTO(S2);
        LABEL(S2):
            ... do stuff 2 ...
            GOTO(S1);
        .........
    }
}

просто определив пару макросов. Практически любой FSA можно превратить в структурированный код без использования goto. Я предпочитаю держаться подальше от кода GOTO-in-disguise, потому что он может попасть в те же проблемы со спагетти-кодом, что и неприкрытые gotos.

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

Точно. Кроме того, я полностью согласен с тем, что хорошие программисты должны знать, когда не следует применять общие правила.

Tamas Czinege 22.12.2008 23:20

За восемь лет программирования я много использовал goto, большая часть этого была в первый год, когда я использовал версию GW-BASIC и книгу 1980 года, в которой не было ясно, что goto следует использовать только в некоторых случаях. Единственный раз, когда я использовал goto в C++, - это когда у меня был код вроде следующего, и я не уверен, есть ли лучший способ.

for (int i=0; i<10; i++) {
    for (int j=0; j<10; j++)
    {
        if (somecondition==true)
        {
            goto finish;
        }
        //Some code
    }
    //Some code
}
finish:

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

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

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

Например:

for(int i_index = start_index; i_index >= 0; --i_index)
{
    for(int j_index = start_index; j_index >=0; --j_index)
        for(int k_index = start_index; k_index >= 0; --k_index)
            if (my_condition)
                goto BREAK_NESTED_LOOP_j_index;
BREAK_NESTED_LOOP_j_index:;
}

Я не знаю, откуда взялась эта вещь о goto ...

На скомпилированном языке все условные инструкции (if, switch, for, while и т. д.) Разрешаются в "cmp", "jmp" или "j ??" в машинном коде (jmp IS goto).

Фактически, действительно хорошо оптимизированный код знает о лучших путях выполнения и ДОЛЖЕН поэтому использовать "goto" ... Даже лучше делать линейный фрагмент кода с goto, чем методы и вызовы, когда стек не используется ("call" = "push" + "jmp").

Нет абсолютно никаких веских причин, по которым вы не должны использовать GOTO в C++: сгенерированный код и так полон "jmp" везде.

Это проблема только в сценариях (где она часто недоступна), потому что назначение goto не обязательно интерпретируется, когда есть инструкция goto.

Первоначальный аргумент против goto состоит в том, что код сложнее проверить.

Это абсурд: Код не предназначен для проверки (или только ОДИН РАЗ): он предназначен для выполнения, чтобы он был небольшим и быстрым. Оптимизированный фрагмент кода должен избегать избыточности (путем повторного использования тех же инструкций) и избегать проверок во время выполнения (следует избегать всех исключений, не связанных с аппаратными сбоями, во время разработки, а НЕ во время выполнения).

Современный код сделан так, чтобы он был читаемым, предсказуемым и поддерживаемым. Goto часто (но не всегда) является признаком того, что этого кода нет. Но глупо утверждать, что это «не для проверки». Большая часть разработки программного обеспечения в наши дни действительно связана с проверкой кода.

phord 22.09.2012 02:27

.. И иногда сложные алгоритмы, описанные в комментариях с шагами, будут НАЧАЛЬНО читабельнее с goto stepX

Петър Петров 22.04.2013 04:54

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

Troyseph 20.10.2015 17:02

Единственные две причины, по которым я использую goto в своем коде на C++:

  • Разрыв вложенных циклов уровня 2+
  • Сложные потоки, подобные этому (комментарий в моей программе):

    /* Analysis algorithm:
    
      1.  if classData [exporter] [classDef with name 'className'] exists, return it,
          else
      2.    if project/target_codename/temp/classmeta/className.xml exist, parse it and go back to 1 as it will succeed.
      3.    if that file don't exists, generate it via haxe -xml, and go back to 1 as it will succeed.
    
    */
    

Для удобства чтения здесь после этого комментария я определил метку step1 и использовал ее на шагах 2 и 3. На самом деле, в более чем 60 исходных файлах только эта ситуация и один 4 уровня вложенности - это те места, где я использовал goto. Всего два места.

Этот пример - просто петля while (! exists(X)) { if (canParse) X = parse else X = generate} return X. Нет необходимости в goto.

Rob11311 05.06.2014 20:39

Кстати, проверьте libpng / freetype / libjpeg, как они ИСПОЛЬЗУЮТ goto там, где производительность критична! goto МОЖЕТ НЕМНОГО увеличить производительность.

Петър Петров 11.06.2014 14:53

Другие вопросы по теме