Использовать GOTO или нет?

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

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

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

Тем не менее, я прочитал много мест, в которых плохая практика имеет инструкции goto.

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

Какой путь прав?


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

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

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

Ответ 1

Я не уверен, что вы подразумеваете под очисткой кода, но на С++ существует концепция, называемая "сбор ресурсов, инициализация", и ответственность за очистку данных должна зависеть от ваших деструкторов.

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

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

EDIT. Позвольте мне немного прояснить это.

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

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 или что-то подобное, потому что пример выше, поскольку он явно несовершенен.

Ответ 2

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

Ответ 3

В отношении gotos и вашего кода в основном используются две точки:

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

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

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

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

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

Ответ 4

Ваш код крайне не идиоматичен, и вы никогда не должны его писать. Вы в основном имитируете 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;
}

Ответ 6

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

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, если вы можете.

Ответ 7

Ваш пример не является безопасным для исключений.

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

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

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

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

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

Ответ 8

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

Как выглядит ваш код:

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

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

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

Как используется в ядре Linux, goto, используемый для очистки, хорошо работает, когда одна функция выполняет два или более шага, которые могут быть отменены. Шаги не обязательно должны быть распределением памяти. Это может быть изменение конфигурации для части кода или в регистре чипсета ввода-вывода. 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. И, наконец, некоторые люди не используют исключения в своем коде и инструктируйте компиляторы отключить их. Таким образом, не весь код должен быть безопасным для исключений.

Ответ 13

Недостаток 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-mask, - это все, что можно было бы превратить в код 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-less. Я предпочитаю держаться подальше от кода GOTO-in-mask, потому что он может попасть в те же проблемы с спагетти-кодом, что и неприкрытые gotos.

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

Ответ 14

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

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

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

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

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

Ответ 16

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, но tail-end-merge-don't-repeat-yourself происходит довольно часто, когда исключение - безопасность не является проблемой.

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

Ответ 17

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

Ответ 18

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

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

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

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

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

Ответ 19

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

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

Ваш код мог бы (т.е. в С++, СЛЕДУЕТ) записываться как:

BOOL foo()
{
   BOOL bRetVal = FALSE;

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

   // Lot of code...

   return bRetVal ;
}

(Обратите внимание, что new-ing int несколько глупо в реальном коде, но вы можете заменить int на какой-либо объект, а затем он имеет больше смысла). Представим себе, что у нас есть объект типа T (T может быть int, некоторым классом С++ и т.д.). Затем код становится:

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

Ответ 20

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

  • Нарушение вложенных циклов уровня 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, Только два места.

Ответ 21

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

Ответ 22

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

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

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

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

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

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

Ответ 23

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

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

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

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

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

Ответ 24

Я не буду говорить, что goto всегда плох, но ваше использование этого, безусловно, есть. Такие "секции очистки" были довольно распространены в начале 1990-х годов, но использование его для нового кода - это чистое зло.

Ответ 25

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

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
   {
      ...
   }
}

Ответ 26

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

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

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

  • Не использовать типы STL, такие как auto_ptr

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

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

Ответ 27

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

Ответ 28

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

Как правило, единственное исключение, которое я делаю из правила "нет 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:;
}

Ответ 29

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

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

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

Ответ 30

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

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

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

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

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