Каковы некоторые лучшие способы избежать do-while (0); взломать С++?

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

if(check())
{
  ...
  ...
  if(check())
  {
    ...
    ...
    if(check())
    {
      ...
      ...
    }
  }
}

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

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Каковы некоторые лучшие способы избежать этого обхода/взлома, чтобы он стал более высокоуровневым (отраслевым) кодом?

Любые предложения, которые из коробки приветствуются!

Ответы

Ответ 1

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

Например:

void foo(...)
{
   if (!condition)
   {
      return;
   }
   ...
   if (!other condition)
   {
      return;
   }
   ...
   if (!another condition)
   {
      return;
   }
   ... 
   if (!yet another condition)
   {
      return;
   }
   ...
   // Some unconditional stuff       
}

Ответ 2

Бывают случаи, когда использование goto на самом деле является ПРАВИЛЬНЫМ ответом - по крайней мере, тем, кто не воспитывается в религиозном убеждении, что "goto никогда не может быть ответом, независимо от того, что вопрос" - и это один из таких случаев.

Этот код использует взломать do { ... } while(0); с единственной целью нанести goto как break. Если вы собираетесь использовать goto, тогда откройте об этом. Не нужно делать код HARDER для чтения.

Особая ситуация возникает, когда у вас много кода с довольно сложными условиями:

void func()
{
   setup of lots of stuff
   ...
   if (condition)
   {
      ... 
      ...
      if (!other condition)
      {
          ...
          if (another condition)
          {
              ... 
              if (yet another condition)
              {
                  ...
                  if (...)
                     ... 
              }
          }
      }
  .... 

  }
  finish up. 
}

На самом деле это может сделать CLEARER правильным кодом, не имея такой сложной логики.

void func()
{
   setup of lots of stuff
   ...
   if (!condition)
   {
      goto finish;
   }
   ... 
   ...
   if (other condition)
   {
      goto finish;
   }
   ...
   if (!another condition)
   {
      goto finish;
   }
   ... 
   if (!yet another condition)
   {
      goto finish;
   }
   ... 
   .... 
   if (...)
         ...    // No need to use goto here. 
 finish:
   finish up. 
}

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

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

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

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

Ответ 3

Вы можете использовать простой шаблон продолжения с переменной bool:

bool goOn;
if ((goOn = check0())) {
    ...
}
if (goOn && (goOn = check1())) {
    ...
}
if (goOn && (goOn = check2())) {
    ...
}
if (goOn && (goOn = check3())) {
    ...
}

Эта цепочка выполнения останавливается, как только checkN возвращает false. Никакие дальнейшие вызовы check...() не выполнялись бы из-за короткого замыкания оператора &&. Более того, оптимизация компиляторов достаточно умна, чтобы признать, что настройка goOn - false - это улица с односторонним движением и вставить отсутствующий goto end для вас. В результате производительность вышеприведенного кода была бы идентична производительности do/while(0), но без болезненного удара по ее читаемости.

Ответ 4

  • Попробуйте извлечь код в отдельную функцию (или, возможно, более одного). Затем вернитесь из функции, если проверка завершилась неудачей.

  • Если он слишком сильно связан с окружающим кодом, и вы не можете найти способ уменьшить сцепление, посмотрите код после этого блока. Предположительно, он очищает некоторые ресурсы, используемые функцией. Попробуйте управлять этими ресурсами, используя RAII; затем замените каждый изворотливый break на return (или throw, если это более подходит), и пусть деструктор объекта очистится для вас.

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

  • Если у вас есть правила кодирования, которые вслепую запрещают goto, и вы действительно не можете упростить поток программы, вам, вероятно, придется замаскировать его с помощью do hack.

Ответ 5

TLDR: RAII, транзакционный код ( только устанавливать результаты или возвращать материал, когда он уже вычислен) и исключения.

Длинный ответ:

В C наилучшей практикой для этого типа кода является добавление в код метки EXIT/CLEANUP/other, где происходит очистка локальных ресурсов, и код ошибки (если есть) вернулся. Это лучшая практика, потому что она естественным образом разделяет код на инициализацию, вычисление, фиксацию и возврат:

error_code_type c_to_refactor(result_type *r)
{
    error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere
    some_resource r1, r2; // , ...;
    if(error_ok != (result = computation1(&r1))) // Allocates local resources
        goto cleanup;
    if(error_ok != (result = computation2(&r2))) // Allocates local resources
        goto cleanup;
    // ...

    // Commit code: all operations succeeded
    *r = computed_value_n;
cleanup:
    free_resource1(r1);
    free_resource2(r2);
    return result;
}

В C в большинстве кодовых кодов код if(error_ok != ... и goto обычно скрывается за некоторыми макросами удобства (RET(computation_result), ENSURE_SUCCESS(computation_result, return_code) и т.д.).

С++ предлагает дополнительные инструменты над C:

  • Функцию блока очистки можно реализовать как RAII, что означает, что вам больше не нужен весь блок cleanup и включение клиентского кода для добавления ранних операторов возврата.

  • Вы бросаете, когда вы не можете продолжать, преобразовывая все if(error_ok != ... в прямые вызовы.

Эквивалентный код С++:

result_type cpp_code()
{
    raii_resource1 r1 = computation1();
    raii_resource2 r2 = computation2();
    // ...
    return computed_value_n;
}

Это наилучшая практика, потому что:

  • Явный (то есть, хотя обработка ошибок не является явной, основной поток алгоритма есть)

  • Просто написать код клиента

  • Минимально

  • Это просто

  • У него нет повторяющихся кодовых конструкций

  • Он не использует макросы

  • Он не использует строгие конструкторы do { ... } while(0)

  • Это многократно используется с минимальными усилиями (то есть, если я хочу скопировать вызов computation2(); в другую функцию, мне не нужно обязательно добавлять do { ... } while(0) в новый код, а не #define макрос goto wrapper и ярлык очистки, и ничего другого).

Ответ 6

Я добавляю ответ ради полноты. В ряде других ответов указывалось, что блок большого состояния можно разделить на отдельную функцию. Но, как было также отмечено несколько раз, этот подход отделяет условный код от исходного контекста. Это одна из причин, по которой lambdas были добавлены к языку на С++ 11. Использование лямбда было предложено другими, но явный образец не был предоставлен. Я поставил один в этом ответе. Меня поражает, что он очень похож на подход do { } while(0) во многих отношениях - и, возможно, это означает, что он все еще скрыт goto....

earlier operations
...
[&]()->void {

    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
    if (!check()) return;
    ...
    ...
}();
later operations

Ответ 7

Конечно, не ответ, а ответ (ради полноты)

Вместо:

do {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

Вы можете написать:

switch (0) {
case 0:
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

Это все еще goto, но по крайней мере это не цикл. Это означает, что вам не нужно очень тщательно проверять, что в блоке нет скрытого continue.

Конструкция также достаточно проста, что вы можете надеяться, что компилятор оптимизирует ее.

Как было предложено @jamesdlin, вы можете даже скрыть это за макросом вроде

#define BLOC switch(0) case 0:

И используйте его как

BLOC {
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
}

Это возможно, потому что синтаксис языка C ожидает утверждения после коммутатора, а не блока в квадратных скобках, и вы можете поставить ярлык case перед этим оператором. До сих пор я не видел смысла разрешать это, но в этом конкретном случае удобно скрыть переключатель за красивым макросом.

Ответ 8

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

void main()
{
    //do stuff always
    func();
    //do other stuff always
}

void func()
{
    if (!condition)
        return;
    ...
    if (!other condition)
        return;
    ...
    if (!another condition)
        return;
    ... 
    if (!yet another condition)
        return;
    ...
}

Ответ 9

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

Ответ 10

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

if (check()) {
  doStuff();
}  
if (stillOk()) {
  doMoreStuff();
}
if (amIStillReallyOk()) {
  doEvenMore();
}

// edit 
doThingsAtEndAndReportErrorStatus()

Ответ 11

Для меня do{...}while(0) отлично. Если вы не хотите видеть do{...}while(0), вы можете определить для них альтернативные ключевые слова.

Пример:

#define BEGIN_TEST do{
#define END_TEST }while(0);

BEGIN_TEST
   if(!condition1) break;
   if(!condition2) break;
   if(!condition3) break;
   if(!condition4) break;
   if(!condition5) break;

   //processing code here

END_TEST

Я думаю, что компилятор удалит ненужное условие while(0) в do{...}while(0) в двоичной версии и преобразует разрывы в безусловный переход. Вы можете проверить версию ассемблера, чтобы убедиться.

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

{
   if(!condition1) goto end_blahblah;
   if(!condition2) goto end_blahblah;
   if(!condition3) goto end_blahblah;
   if(!condition4) goto end_blahblah;
   if(!condition5) goto end_blahblah;

   //processing code here

 }end_blah_blah:;  //use appropriate label here to describe...
                   //  ...the whole code inside the block.

Обратите внимание, что метка помещается после закрытия }. Это предотвращает одну возможную проблему в goto, которая случайно помещает код между ними, потому что вы не видите метку. Теперь это похоже на do{...}while(0) без кода условия.

Чтобы сделать этот код более понятным и понятным, вы можете сделать это:

#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);
   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);
   if(!condition5) FAILED(NormalizeData);

END_TEST(NormalizeData)

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

#define BEGIN_TEST {
#define END_TEST(_test_label_) }_test_label_:;
#define FAILED(_test_label_) goto _test_label_

BEGIN_TEST
   if(!condition1) FAILED(NormalizeData);
   if(!condition2) FAILED(NormalizeData);

   BEGIN_TEST
      if(!conditionAA) FAILED(DecryptBlah);
      if(!conditionBB) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionCC) FAILED(DecryptBlah);

      // --We can now decrypt and do other stuffs.

   END_TEST(DecryptBlah)

   if(!condition3) FAILED(NormalizeData);
   if(!condition4) FAILED(NormalizeData);

   // --other code here

   BEGIN_TEST
      if(!conditionA) FAILED(TrimSpaces);
      if(!conditionB) FAILED(TrimSpaces);
      if(!conditionC) FAILED(NormalizeData);   //Jump out to the outmost block
      if(!conditionD) FAILED(TrimSpaces);

      // --We can now trim completely or do other stuffs.

   END_TEST(TrimSpaces)

   // --Other code here...

   if(!condition5) FAILED(NormalizeData);

   //Ok, we got here. We can now process what we need to process.

END_TEST(NormalizeData)

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

Ответ 12

Подобно запросу dasblinkenlight, но избегает назначения внутри if, которое может быть исправлено рецензентами кода:

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}

...

Я использую этот шаблон, когда результаты шага необходимо проверить перед следующим шагом, который отличается от ситуации, когда все проверки могут выполняться спереди с большим шаблоном типа if( check1() && check2()....

Ответ 13

Использовать исключения. Ваш код будет выглядеть намного более чистым (и исключения были созданы точно для обработки ошибок в потоке выполнения программы). Для очистки ресурсов (файловые дескрипторы, подключения к базе данных и т.д.) Прочитайте статью Почему С++ не предоставляет конструкцию "finally" ?.

#include <iostream>
#include <stdexcept>   // For exception, runtime_error, out_of_range

int main () {
    try {
        if (!condition)
            throw std::runtime_error("nope.");
        ...
        if (!other condition)
            throw std::runtime_error("nope again.");
        ...
        if (!another condition)
            throw std::runtime_error("told you.");
        ...
        if (!yet another condition)
            throw std::runtime_error("OK, just forget it...");
    }
    catch (std::runtime_error &e) {
        std::cout << e.what() << std::endl;
    }
    catch (...) {
        std::cout << "Caught an unknown exception\n";
    }
    return 0;
}

Ответ 14

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

В ответ на комментарий, который я получил ниже, я отредактировал свое введение здесь: вы можете найти полную информацию о реализации C++ monads in различные места, которые позволят вам достичь того, что предлагает Rotsor. Это займет некоторое время, чтобы собрать монады, поэтому вместо этого я предлагаю вам быстрый "монашеский" механизм "бедных", для которого вам нужно знать не более, чем boost:: optional.

Настройте шаги вычисления следующим образом:

boost::optional<EnabledContext> enabled(boost::optional<Context> context);
boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);

Каждый вычислительный шаг, очевидно, может сделать что-то вроде return boost::none, если необязательный параметр был пуст. Так, например:

struct Context { std::string coordinates_filename; /* ... */ };

struct EnabledContext { int x; int y; int z; /* ... */ };

boost::optional<EnabledContext> enabled(boost::optional<Context> c) {
   if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads
   if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered.
   EnabledContext ec;
   std::ifstream file_in((*c).coordinates_filename.c_str());
   file_in >> ec.x >> ec.y >> ec.z;
   return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value.
}

Затем соедините их вместе:

Context context("planet_surface.txt", ...); // Close over all needed bits and pieces

boost::optional<EnergisedContext> result(energised(enabled(context)));
if (result) { // A single level "if" statement
    // do work on *result
} else {
    // error
}

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

Если вы не заботитесь о неизменности, и удобнее возвращать один и тот же объект каждый раз, когда вы можете придумать некоторые варианты с использованием shared_ptr или т.п.

Ответ 15

Как перемещать инструкции if в дополнительную функцию, получая результат численного или перечисления?

int ConditionCode (void) {
   if (condition1)
      return 1;
   if (condition2)
      return 2;
   ...
   return 0;
}


void MyFunc (void) {
   switch (ConditionCode ()) {
      case 1:
         ...
         break;

      case 2:
         ...
         break;

      ...

      default:
         ...
         break;
   }
}

Ответ 16

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

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

Например,

if (conditionA) {
    ....
    if (conditionB) {
        ....
        if (conditionC) {
            myLogic();
        }
    }
}

Посмотрев на закрывающие блоки, легко узнать, что myLogic() происходит только тогда, когда conditionA and conditionB and conditionC истинно.

Это становится намного менее заметным, когда есть ранние возвращения:

if (conditionA) {
    ....
    if (!conditionB) {
        return;
    }
    if (!conditionD) {
        return;
    }
    if (conditionC) {
        myLogic();
    }
}

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

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

if (conditionA) {
    isA = true;
    ....
}

if (isA && conditionB) {
    isB = true;
    ...
}

if (isB && conditionC) {
    isC = true;
    myLogic();
}

(Конечно, рекомендуется использовать одну и ту же переменную, чтобы заменить все isA isB isC.)

Такой подход, по крайней мере, даст читателю кода, что myLogic() выполняется, когда isB && conditionC. Читателю дается намек на то, что ему нужно продолжить поиск, что приведет к тому, что isB будет истинным.

Ответ 17

Что-то вроде этого возможно

#define EVER ;;

for(EVER)
{
    if(!check()) break;
}

или использовать исключения

try
{
    for(;;)
        if(!check()) throw 1;
}
catch()
{
}

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

Ответ 18

typedef bool (*Checker)();

Checker * checkers[]={
 &checker0,&checker1,.....,&checkerN,NULL
};

bool checker1(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

bool checker2(){
  if(condition){
    .....
    .....
    return true;
  }
  return false;
}

......

void doCheck(){
  Checker ** checker = checkers;
  while( *checker && (*checker)())
    checker++;
}

Как насчет этого?

Ответ 19

Я не программист C++, поэтому я не буду писать код здесь, но пока никто не упомянул объект ориентированного решения. Итак, вот моя догадка:

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

Хорошо, что такой дизайн очень хорошо подходит к принципу open/closed, поскольку вы можете легко добавлять новые условия во время инициализации объект, содержащий данный метод. Вы даже можете добавить второй метод к интерфейсу с методом оценки условий, возвращающим описание условия. Это можно использовать для самодокументирующих систем.

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

Ответ 20

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

    private ResultCode DoEverything()
    {
        ResultCode processResult = ResultCode.FAILURE;
        if (DoStep1() != ResultCode.SUCCESSFUL)
        {
            Step1FailureCleanup();
        }
        else if (DoStep2() != ResultCode.SUCCESSFUL)
        {
            Step2FailureCleanup();
            processResult = ResultCode.SPECIFIC_FAILURE;
        }
        else if (DoStep3() != ResultCode.SUCCESSFUL)
        {
            Step3FailureCleanup();
        }
        ...
        else
        {
            processResult = ResultCode.SUCCESSFUL;
        }
        return processResult;
    }

Ответ 21

Во-первых, краткий пример, чтобы показать, почему goto не является хорошим решением для С++:

struct Bar {
    Bar();
};

extern bool check();

void foo()
{
    if (!check())
       goto out;

    Bar x;

    out:
}

Попробуйте скомпилировать это в объектный файл и посмотреть, что произойдет. Затем попробуйте эквивалент do + break + while(0).

Это было в стороне. Далее следует главное.

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

Один из вариантов получения этой семантики - RAII; см. @utnapistim ответ. С++ гарантирует, что автоматические деструкторы работают в обратном порядке для конструкторов, которые, естественно, обеспечивают "разматывание".

Но для этого требуется много классов RAII. Иногда проще использовать только стек:

bool calc1()
{
    if (!check())
        return false;

    // ... Do stuff1 here ...

    if (!calc2()) {
        // ... Undo stuff1 here ...
        return false;
    }

    return true;
}

bool calc2()
{
    if (!check())
        return false;

    // ... Do stuff2 here ...

    if (!calc3()) {
        // ... Undo stuff2 here ...
        return false;
    }

    return true;
}

... и так далее. Это легко проверить, так как он помещает код "отменить" рядом с кодом "do". Легкий аудит хорош. Это также делает поток управления очень ясным. Это также полезная модель для C.

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

Ответ 22

Так я это делаю.

void func() {
  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...

  if (!check()) return;
  ...
  ...
}

Ответ 23

Если код имеет длинный блок if..else if..else, вы можете попробовать и переписать весь блок с помощью Functors или function pointers. Это может быть неправильное решение всегда, но довольно часто.

http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html

Ответ 24

Консолидируйте его в один оператор if:

if(
    condition
    && other_condition
    && another_condition
    && yet_another_condition
    && ...
) {
        if (final_cond){
            //Do stuff
        } else {
            //Do other stuff
        }
}

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

Ответ 25

Меня поражает количество различных ответов, представленных здесь. Но, наконец, в коде, который мне нужно изменить (т.е. Удалите этот трюк do-while(0) или что-то еще), я сделал что-то отличное от любого из упоминаемых здесь ответов, и я смущен, почему никто не подумал об этом. Вот что я сделал:

Исходный код:

do {

    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
    if(!check()) break;
    ...
    ...
} while(0);

finishingUpStuff.

Сейчас:

finish(params)
{
  ...
  ...
}

if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...
if(!check()){
    finish(params);    
    return;
}
...
...

Итак, что было сделано здесь, так это то, что отделка была изолирована в функции, и все вдруг стало таким простым и чистым!

Я думал, что это решение стоит упомянуть, поэтому предоставил его здесь.

Ответ 26

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

if(
    DoSomething() &&
    DoSomethingElse() &&
    DoAThirdThing() )
{
    // do good condition action
}
else
{
    // handle error
}

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

Ответ 27

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

//you can use something like this (pseudocode)
long var = 0;
if(condition)  flag a bit in var
if(condition)  flag another bit in var
if(condition)  flag another bit in var
............
if(var == certain number) {
Do the required task
}