Избегайте дублирования кода - PullRequest
9 голосов
/ 25 июня 2010

скажем, у меня есть:

  switch( choice ) {
  case A:   
     stmt;
     do_stmt_related2A;
  break;

  case B:
     stmt;
     do_stmt_related2B;
  break;

  case C: something_different();
   ...
  }

Как можно избежать дублирования кода stmt?

Но есть ли обходной путь?Метка расширения gcc как значение выглядит довольно хорошо для такой ситуации.

   switch( choice ) {
     do {
     case A:  ptr = &&A_label;
     break;
     case B:  ptr = &&B_label;
     } while(0);
              stmt;
              goto *ptr;
     case C: ...

Есть ли какой-нибудь прием, который может сделать то же самое в ANSI-C?Изменить: Конечно, я подумал о функции / макрос / встроенный.Но что-нибудь еще?Дело не в производительности тоже.Просто для образовательных целей.;)

Ответы [ 15 ]

0 голосов
/ 03 июля 2010

Если вы используете gcc, один из вариантов - вложенные функции .Это функции, которые имеют доступ ко всем переменным родительской функции.

Например:

void foo(int bar) {

    int x = 0;
    void stmt(void) { //nested function!!
        x++;
        if (x == 8) {
            x = 0;
        }
    }

    switch( bar ) {
    case A:   
        stmt();
        do_stmt_related2A;
    break;

    case B:
        stmt();
        do_stmt_related2B;
    break;

    case C:
        something_different();
        ...
    break;
    }
}

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

Если у вас есть исполняемый стек, вы даже можете создать указатель на вложенную функцию.Это очень похоже на лямбда-функцию, кроме ужасной - или ужасно веселой, в зависимости от вашей точки зрения.(Как и все указатели на «локальные» объекты, указатель становится недействительным при выходе из родительской функции! Осторожно!)

0 голосов
/ 01 июля 2010

Вот одна альтернатива вызовам функций или операторам вторичного переключателя:

isA=false;
switch( choice ) { 
  case A:    
    isA=true;
  //nobreak
  case B: 
    stmt; 
    isA ? do_stmt_related2A : do_stmt_related2B;
  break; 
  case C: 
    something_different(); 
  break;
} 

Однако я не могу сказать, что действительно рекомендую его как стиль кодирования.

0 голосов
/ 30 июня 2010

Вам просто нужны две управляющие структуры.Один для диктовки выполнения инструкций первого порядка, второй для инструкций второго порядка.

switch (state) {
    case A:
    case B:
        stmt;

        switch (state) {
            case A:
                do_stmt_related2A;
                break;
            case B:
                do_stmt_related2B;
                break;
        }

        break;

    case C:
        something_different();
        ...
}

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

0 голосов
/ 29 июня 2010

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

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

Рефакторинг stmt в static функцию, вероятно, даст хорошие результаты, если stmt действительно дорогой или большой код.

Еще одна вещь, которую вы могли бы попробовать, если бы вы могли вывести stmt из переключателя / корпуса и просто выполнить, всегда выполнять ее.Иногда это самый дешевый способ, но это действительно зависит от того, что на самом деле делает * 1012.

Еще одна вещь, которую вы можете сделать, это рефакторинг всех stmt, do_stmt_related2A и do_stmt_related2A всехв файл static функций, например:

// args in this is just a psudocode place holder for whatever arguments are needed, and 
// not valide C code.
static void stmt_f(void (*continuation)(arg_list), args) {
   stmt; // This corresponds almost exactly to stmt in your code
   continuation(args);
}
static void do_stmt_related2A_f(args) {
    do_stmt_related2A;
}
static void do_stmp_related2B_f(args) {
    do_stmt_related2B;
}

...
    switch (condition) {
       case A:
          stmt_f(do_stmt_related2A_f, args);
          break;
       case B:
    ...

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

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

О, последнее, что вы можете попробовать, если вы контролируете, какие фактические значения A, B, C ... могут принять, тогда вы можете убедиться, чтоу тех с подобными префиксами есть смежные значения.Если A и B действительно находятся рядом друг с другом и если компилятор решит, что ему нужно разбить переключатель / регистр на несколько разных таблиц переходов, то он, скорее всего, поместит A и B в одну таблицу переходов и также увидит, что они имеюттот же префикс и поднять этот код для вас.Это более вероятно, если C, который не разделяет этот префикс, не смежен с A или B, но тогда ваш общий код может быть хуже.

0 голосов
/ 25 июня 2010

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

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

Обратите также внимание, что my_choice - это intmax_t, поэтому он должен быть совместим с любым типом choice.

(Вставка for просто для удовольствия, и, очевидно, будет работать только с C99. Вы можете заменить его на дополнительный {} вокруг всего и просто объявить my_choice для C89.)

typedef enum { one = 1, two, three } number;

int main(int argc, char** argv) {

  number choice = (number)argc;

  for (intmax_t _0 = 0, my_choice = choice; !_0; ++_0)
    switch(my_choice) {
    case one:;
    case two:;
      {
        printf("Do whatever you want here\n");
        switch (my_choice) {
        case one:
          printf("test 1\n");
          break;
        case two:
          printf("test 2\n");
          break;
        }
        printf("end special\n");
      }
      break;
    default:;
      printf("test %d, default\n", choice);
    }
}
...