Это плохая практика, чтобы определение макроса возвращало значение для функции? - PullRequest
6 голосов
/ 10 августа 2011

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

Пример использования, который я рассматриваю, - записьпроверка значений и ошибок, например:

#define WRITE_CHK(file, param)\
if (!write_that_returns_zero_on_fail(file, param)) {\
   handle_error();\
   return false;\
}

код клиента:

bool myfunc()
{
   ...
   WRITE_CHK(file, param) // function might return here
   ...
   return true;
}

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

Ответы [ 4 ]

15 голосов
/ 10 августа 2011

Стандартный ответ «не используйте макросы»;но это часто немного упрощенно.Иногда бывают случаи, когда они могут значительно сократить многословность, которая у вас была бы в противном случае.

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

3 голосов
/ 10 августа 2011

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

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

// assume handle_error returns true
#define WRITE_FAILED(file, param)\
(!write_that_returns_zero_on_fail(file, param) && handle_error())

// macro usage
if(WRITE_FAILED(file, param))
{
    return;
}

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

1 голос
/ 10 августа 2011

Создание двухэтапного макроса:

#define WRITE_CHK_(file, param, HANDLER)\
if (!write_that_returns_zero_on_fail(file, param)) {\
    handle_error();\
    HANDLER\
}

#define RFALSE return false;

#define WRITE_CHK(file, param) WRITE_CHK_(file, param, RFALSE)

Затем вы можете сделать другие обертки, если вам нужна аналогичная проверка в другом месте (или напрямую использовать WRITE_CHK(file, param, return false))

0 голосов
/ 10 августа 2011

Да, это плохо. Используйте goto.

 #define fail_unless(x) do {if(!(x)) goto fail;} while (0)

 int foo()
 {
     fail_unless( my_write(...) );
     fail_unless( bar() );
     return 0;
 fail:
     cleanup();
     return -1;
 }

Редактировать : если вы отрицаете голосование, потому что не понимаете, что там делает цикл while, вы должны были просто спросить. Это стандартная техника, позволяющая убедиться, что макрос C действует как отдельный оператор в любом контексте. Если ваш макрос расширяется до оператора if (например, макросы в другом ответе с голосованием), вы получаете неожиданные побочные эффекты, например:

 #define DONT_DO_THIS(x) if (!x) { foo; }

 if (a) DONT_DO_THIS(x)
 else something_else();

Вы ожидаете, что что-то будет выполнено, когда !a, но на самом деле макрос расширяется до:

 if (a)
     if (!x) { foo;}
     else something_else();

И что-то выполняется, когда a && x, а не когда !a, как задумал программист.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...