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

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

30 ответов


Я не уверен, что вы подразумеваете под очисткой кода, но в C++ есть концепция под названием"получение ресурса есть инициализация

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

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

редактировать: позвольте мне прояснить это немного.

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

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


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


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

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

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

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

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

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


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

код не работает как вы ожидаете, потому что это:
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;
}


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

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


ваш пример не является исключением.

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

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

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

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

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


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

как должен выглядеть ваш код:

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

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

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

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

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


как используется в ядре 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;

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


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

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


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


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


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

1) Когда вы должны использовать Гото, как в АСМ или внутри .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:

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

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-in-Maske, потому что он может попасть в те же проблемы с кодом спагетти, что и неприкрытые goto.

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


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 происходит довольно часто, когда exception-safety не является вопрос.

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


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

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

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

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

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


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


вся цель идиомы every-function-has-a-single-exit-point в C заключалась в том, чтобы поместить все вещи очистки в одном месте. Если вы используете деструкторы C++ для обработки очистки, это больше не нужно-очистка будет выполнена независимо от того, сколько точек выхода имеет функция. Таким образом, в правильно разработанном коде C++ больше нет необходимости в таких вещах.


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

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

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

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

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


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

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

ваш код может (т. е. на C++, должен) быть написан as:

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


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


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


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

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

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

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

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


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


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

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

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

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

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

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

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

  • не пользуясь STL типы, такие как auto_ptr

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

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


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

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

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

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

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


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

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

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

функции (вызовы 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;
  }