Do {...} while (false)

Я смотрел какой-то код от человека и заметил, что у него, похоже, есть шаблон в его функциях:

<return-type> function(<params>)
{
 <initialization>

 do
 {
   <main code for function>
 }
 while(false);

 <tidy-up & return>
}

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

Ответ 1

Вы можете break выйти из do{...}while(false).

Ответ 2

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

В макросе, OTOH, do {something; } while (false) - это удобный способ FORCE с запятой после вызова макроса, абсолютно никакой другой токен не может следовать.

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

Ответ 3

Перерыв как goto, вероятно, является ответом, но я выдвину еще одну идею.

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

Помните, что в последнее время С++ допускает {...} в любом месте, это не всегда так.

Ответ 4

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

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

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

Ответ 5

Я видел такой код, поэтому вы можете использовать break как goto.

Ответ 6

Это просто извращение while для получения семантики goto tidy-up без использования слова goto.

Это плохая форма, потому что, когда вы используете другие циклы внутри внешнего while, breaks становятся двусмысленными для читателя. "Предполагается, что он должен выйти?" Или это предназначено только для выхода из внутреннего цикла? "

Ответ 7

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

Ответ 8

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

Тем не менее, я заметил, очень часто эти циклы do..while имеют тенденцию к росту. И затем они получают внутри if и else, что делает код на самом деле не очень читаемым, не говоря уже о проверке.

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

Ответ 9

Мне удобнее писать break вместо goto end. Вам даже не нужно придумывать имя для ярлыка, которое делает намерение более ясным: вы не хотите переходить на метку с определенным именем. Вы хотите выбраться отсюда.

Кроме того, возможно, вам понадобятся брекеты. Итак, это версия do{...}while(false);:

do {
   // code
   if (condition) break; // or continue
   // more code
} while(false);

И так вы должны были бы выразить это, если хотите использовать goto:

{
   // code
   if (condition) goto end;
   // more code
}
end:

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


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

Ответ 10

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

Его главный недостаток по сравнению с идиомой С++ заключается в том, что он не будет убирать, если в тело будет выброшено исключение. У C не было исключений, поэтому это не было проблемой, но это делает его плохой привычкой в ​​С++.

Ответ 11

Может быть, он используется так, чтобы break можно было использовать внутри, чтобы прервать выполнение дополнительного кода в любой точке:

do {
    if (!condition1) break;
    some_code;
    if (!condition2) break;
    some_further_code;
    // …
} while(false);

Ответ 12

Я думаю, что это делается для использования операторов break или continue. Некоторая логика кода "goto".

Ответ 13

Это просто: Очевидно, вы можете выпрыгнуть из фальшивого цикла в любое время, используя оператор break. Кроме того, блок do является отдельной областью (которая также может быть достигнута только с помощью { ... }).

В такой ситуации может быть лучше использовать RAII (объекты автоматически разрушаются правильно, когда функция заканчивается). Другой подобной конструкцией является использование goto - да, я знаю это зло, но его можно использовать, чтобы иметь общий код очистки, например

<return-type> function(<params>)
{
 <initialization>

 <main code for function using "goto error;" if something goes wrong>

 <tidy-up in success case & return>

 error:

 <commmon tidy-up actions for error case & return error code or throw exception>
}

(В стороне: конструктор do-while-false используется в Lua, чтобы найти отсутствующий оператор continue.)

Ответ 14

Многие ответчики объяснили причину do{(...)break;}while(false). Я хотел бы дополнить эту картину еще одним примером реальной жизни.

В следующем коде мне пришлось установить enumerator operation на основе адреса, на который указывает указатель data. Поскольку ящик коммутатора можно использовать только для скалярных типов, я сначала сделал это неэффективно.

if (data == &array[o1])
    operation = O1;
else if (data == &array[o2])
    operation = O2;
else if (data == &array[on])
    operation = ON;

Log("operation:",operation);

Но поскольку Log() и остальная часть кода повторяются для любого выбранного значения операции, я блуждал, как пропустить остальные сравнения, когда адрес уже обнаружен. И вот здесь do{(...)break;}while(false) пригодится.

do {
    if (data == &array[o1]) {
        operation = O1;
        break;
    }
    if (data == &array[o2]) {
        operation = O2;
        break;
    }
    if (data == &array[on]) {
        operation = ON;
        break;
    }
} while (false);

Log("operation:",operation);

Можно задаться вопросом, почему он не мог сделать то же самое с break в выражении if, например:

if (data == &array[o1])
{
    operation = O1;
    break;
}
else if (...)

break взаимодействует исключительно с ближайшим окружением loop или переключателем, будь то тип for, while или do .. while, поэтому, к сожалению, это не сработает.

Ответ 15

Сколько лет был автор?

Я спрашиваю, потому что однажды я наткнулся на какой-то код Fortran в реальном времени, который сделал это, еще в конце 80-х. Оказывается, это действительно хороший способ имитировать потоки на ОС, которые их не имеют. Вы просто помещаете всю программу (ваш планировщик) в цикл и называете свои "потоковые" подпрограммы "один за другим". Процедуры потоков сами по себе являются циклами, которые повторяются до тех пор, пока не произойдет один из нескольких условий (часто один из них является определенным количеством прошло время). Это "совместная многозадачность", так как отдельные потоки позволяют отказываться от процессора каждый раз, а затем, чтобы другие не истощались. Вы можете вставлять вызовы подпрограмм цикла для имитации приоритета потока полосы.

Ответ 16

В дополнение к уже упомянутым "примерам goto", do... while (0) идиома иногда используется в определении макроса, чтобы обеспечить скобки в определении, и все еще иметь работу компилятора с добавлением полуколонии к конец макросообщения.

http://groups.google.com/group/comp.soft-sys.ace/browse_thread/thread/52f670f1292f30a4?tvc=2&q=while+(0)

Ответ 17

Несколько объяснений. Первый - общий, второй - для макросов препроцессора C с параметрами:

Управление потоком

Я видел, что это используется в простом коде C. В принципе, это более безопасная версия goto, поскольку вы можете вырваться из нее, и вся память очищается должным образом.

Почему что-то goto -подобно быть хорошим? Ну, если у вас есть код, в котором почти каждая строка может возвращать ошибку, но вам нужно реагировать на все одинаково (например, передав сообщение об ошибке вашему вызывающему абоненту после очистки), это обычно более читаемо, чтобы избежать if( error ) { /* cleanup and error string generation and return here */ }, поскольку это позволяет избежать дублирования кода очистки.

Однако в С++ у вас есть исключения + RAII для этой цели, поэтому я бы счел это плохой стиль кодирования.

Проверка с запятой

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

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");

Это случайно называется

if( foo )
    PRINT_IF_DEBUGMODE_ON("Hullo\n")
else
    doSomethingElse();

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

Предоставление области для временных переменных.

Так как do/while имеет фигурные скобки, временные переменные имеют четко определенный масштаб, который они не могут избежать.

Предотвращение предупреждений "возможно нежелательной точки с запятой"

Некоторые макросы активируются только в сборках отладки. Вы определяете их как:

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n) 
#endif

Теперь, если вы используете это в сборке релиза внутри условного выражения, он компилируется в

if( foo )
    ;

Многие компиляторы видят это как те же, что и

if( foo );

Это часто записывается случайно. Поэтому вы получаете предупреждение. Do {} while (false) скрывает это от компилятора и принимается им как указание на то, что вы действительно ничего не хотите здесь делать.

Предотвращение захвата строк условными выражениями

Макрос из предыдущего примера:

if( foo )
    DBG_PRINT_NUM(42)
doSomething();

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

if( foo )

doSomething();

Или более четко отформатированный

if( foo )
    doSomething();

Это совсем не то, что было предназначено. Добавление do {...} while (false) вокруг макроса превращает недостающую точку с запятой в ошибку компиляции.

Что это значит для OP?

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

Ответ 18

Другая причина, по которой я могу думать, заключается в том, что она украшает фигурные скобки, в то время как я верю в более новые стандартные контуры С++, которые не подходят (ISO C им не нравится). В противном случае, чтобы успокоить статический анализатор, например, lint.

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

См. Trivial Do While loop и Подтяжки хороши из C2.

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

Голые фигурные скобки:

init();
...
{
c = NULL;
mkwidget(&c);
finishwidget(&c);
}
shutdown();

Пустые фигурные скобки (NOP):

{}

например.

while (1)
   {}  /* Do nothing, endless loop */

Блок:

if (finished)
{
     closewindows(&windows);
     freememory(&cache);
}

который станет

if (finished)
     closewindows(&windows);
freememory(&cache);

если фигурные скобки удалены, тем самым изменяя поток выполнения, а не только область локальных переменных. Таким образом, не "автономный" или "голый".

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

Ответ 19

Это изобретательный способ эмулировать a GOTO, поскольку эти два практически идентичны:

// NOTE: This is discouraged!
do {
    if (someCondition) break;
    // some code be here
} while (false);
// more code be here

и

// NOTE: This is discouraged, too!
if (someCondition) goto marker;
// some code be here
marker:
// more code be here

С другой стороны, обе эти функции должны выполняться с помощью if s:

if (!someCondition) {
    // some code be here
}
// more code be here

Хотя вложенность может стать немного уродливой, если вы просто превратите длинную строку forward- GOTO в вложенные if s. Реальный ответ - это правильный рефакторинг, хотя и не имитирующий архаичные языковые конструкции.

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

Я не знаю ни одного C-подобного языка, где do/while действительно является идиоматическим решением для чего угодно.

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

Ответ 20

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

Я также видел эту конструкцию, используемую в смешанных средах C/С++, как исключение для человека. "Do {} while (false)" с "break" можно использовать для перехода в конец блока кода, если в цикле будет встречено что-то, что обычно требует исключения.

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

Ответ 21

Я работаю с Adobe InDesign SDK, а примеры InDesign SDK имеют почти каждую функцию, написанную так. Это связано с тем, что функция обычно очень длинная. Где вам нужно выполнить QueryInterface (...), чтобы получить что-либо из объектной модели приложения. Поэтому обычно каждый QueryInterface сопровождается if, который не прошел хорошо, сломался.

Ответ 22

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

Ответ 23

Некоторые кодеры предпочитают иметь только один выход/возврат из своих функций. Использование манекена do {....} while (false); позволяет вам "вырваться" из фиктивной петли, как только вы закончите, и все еще имеете один возврат.

Я java-кодер, поэтому мой пример будет похож на

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class p45
{
    static List<String> cakeNames = Arrays.asList("schwarzwald torte", "princess", "icecream");
    static Set<Integer> forbidden = Stream.of(0, 2).collect(Collectors.toSet());

    public static  void main(String[] argv)
    {
        for (int i = 0; i < 4; i++)
        {
            System.out.println(String.format("cake(%d)=\"%s\"", i, describeCake(i)));
        }
    }


    static String describeCake(int typeOfCake)
    {
        String result = "unknown";
        do {
            // ensure type of cake is valid
            if (typeOfCake < 0 || typeOfCake >= cakeNames.size()) break;

            if (forbidden.contains(typeOfCake)) {
                result = "not for you!!";
                break;
            }

            result = cakeNames.get(typeOfCake);
        } while (false);
        return result;
    }
}

Ответ 24

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

while(true)
{
   <main code for function>
   break; // at the end.
}