#ifdef vs #if-что лучше / безопаснее в качестве метода включения / отключения компиляции определенных разделов кода?



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



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



//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
int someVariable = 5;

#if(DEBUG_ENABLED)
printf("Debugging: someVariable == %d", someVariable);
#endif
}


некоторые из команды предпочитают следующее:



// #define DEBUG_ENABLED

...

SomeFunction()
{
int someVariable = 5;

#ifdef DEBUG_ENABLED
printf("Debugging: someVariable == %d", someVariable);
#endif
}


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

733   18  

18 ответов:

моя первая реакция была #ifdef конечно, но я думаю #if на самом деле имеет некоторые существенные преимущества для этого - вот почему:

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... вместо...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

во-вторых, вы находитесь в лучшем положении, если вы хотите мигрировать от А #define к глобальной константе. #defines обычно не одобряются большинством программистов на C++.

и, в-третьих, вы говорите, что у вас есть разделение в вашей команде. Я предполагаю, что это означает, что разные члены уже приняли разные подходы, и вам нужно стандартизировать. Решение о том, что #if является предпочтительным выбором означает, что код с помощью #ifdef будет компилироваться - и работать-даже когда DEBUG_ENABLED ложно. И это много легче отследить и удалить отладочный вывод, что производится тогда, когда этого не должно быть, чем наоборот.

О, и небольшая точка читаемости. Вы должны быть в состоянии использовать true и false, а не 0/1 в #define, и поскольку значение является одним лексическим маркером, это единственный раз, когда вам не нужны круглые скобки вокруг него.

#define DEBUG_ENABLED true

вместо

#define DEBUG_ENABLED (1)

Они оба отвратительные. Вместо этого сделайте следующее:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

затем, когда вам нужен отладочный код, поместите его внутрь D();. И ваша программа не загрязнена отвратительными лабиринтами #ifdef.

#ifdef просто проверяет, определен ли токен, учитывая

#define FOO 0

затем

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

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

если бы у вас была функция.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

но тогда вы забыли включить файл заголовка в файл.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

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

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

если вы изменили выше функцию.ч:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

но потом вы снова забыли включить файл заголовка в файл.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

препроцессор была бы ошибка из-за использования неопределенного макроса функции.

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

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

для выполнения условной компиляции #if и #ifdef являются почти то же, но не совсем. Если ваша условная компиляция зависит от двух символов, то #ifdef также не будет работать. Например, предположим, что у вас есть два условных символа компиляции, PRO_VERSION и TRIAL_VERSION, вы можете иметь что-то вроде этого:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

использование #ifdef выше становится намного сложнее, особенно получить #else часть для работы.

Я работаю над кодом, который широко использует условную компиляцию, и у нас есть смесь #if & #ifdef. Мы склонны использовать #ifdef / #ifndef для простого случая и #if всякий раз, когда два или более символов оцениваются.

Я сам предпочитаю:

#if defined(DEBUG_ENABLED)

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

#if !defined(DEBUG_ENABLED)

и

#ifndef(DEBUG_ENABLED)

Это вопрос стиля. Но я рекомендую более краткий способ сделать это:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

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

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

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);

#if дает вам возможность установить его в 0, чтобы отключить функциональность, все еще обнаруживая, что переключатель есть.
Лично я всегда #define DEBUG 1 Так что я могу поймать его с помощью #if или #ifdef

#if и #define MY_MACRO (0)

использование #if означает, что вы создали макрос "define", т. е. что-то, что будет искать в коде, который будет заменен на "(0)". Это "макро-ад", который я ненавижу видеть в C++, потому что он загрязняет код потенциальными модификациями кода.

например:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

дает следующую ошибку на g++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

только один ошибка.

это означает, что ваш макрос успешно взаимодействовал с вашим кодом на C++: вызов функции был успешным. В этом простом случае, это забавно. Но мой собственный опыт с макросами, играющими молча с моим кодом, не полон радости и полноты, поэтому...

#ifdef и #define MY_MACRO

использование #ifdef означает, что вы" определяете " что-то. Не то чтобы вы придавали ему значение. Он все еще загрязняет, но, по крайней мере, он будет "заменен ничем" и не будет рассматриваться кодом C++ как оператор lagitimate code. Тот же код выше, с простым определением, это:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

дает следующие предупреждения:

main.cpp||In function ‘int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function ‘int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

так...

вывод

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

но, по крайней мере, мне нравится делать их наименее интерактивными с моим законным кодом C++. Что означает использование #define без значения, используя #ifdef и #ifndef (или даже #если определено как предложенный Джимом баком), и прежде всего, давая им имена так долго и так чуждо никто в здравом уме не будет использовать его "случайно", и что никоим образом это не повлияет на законный код C++.

Post Scriptum

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

#define MY_MACRO @@@@@@@@@@@@@@@@@@

это может быть использовано с #ifdef и #ifndef, но не позволяет компилировать код, если используется внутри функции... Я попробовал это успешно на g++, и это дало ошибку:

main.cpp|410|error: stray ‘@’ in program|

интересные. : -)

первое кажется мне более ясным. Кажется более естественным сделать его флагом по сравнению с определенным / не определенным.

оба точно эквивалентны. В идиоматическом использовании #ifdef используется только для проверки определенности (и того, что я бы использовал в вашем примере), тогда как #if используется в более сложных выражениях, таких как #if defined(A) && !определено(B).

немного OT, но включение/выключение ведения журнала с препроцессором определенно неоптимально в C++. Есть хорошие инструменты ведения журнала, такие как Apache log4cxx которые являются открытым исходным кодом и не ограничивают распространение вашего приложения. Они также позволяют изменять уровни ведения журнала без перекомпиляции, имеют очень низкие накладные расходы, если вы отключите выход из системы, и дают вам возможность полностью отключить выход из системы в производстве.

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

#ifdef macro

означает: "если макрос определен" или "если макрос не существует". Значение макроса здесь не имеет значения. Это может быть что угодно.

#if macro

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

#if macro !=0

пример использования #если

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

теперь вы можете либо поместить определение CFLAG_EDITION либо в свой код

#define CFLAG_EDITION 1 

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

Я всегда использовал # ifdef и флаги компилятора, чтобы определить его...

кроме того, вы можете объявить глобальную константу и использовать C++ if вместо препроцессора #if. Компилятор должен оптимизировать неиспользуемые ветви для вас, и ваш код будет чище.

вот что C++ Gotchas Стивен К. Дьюхерст говорит об использовании #if's.

я использовал, чтобы использовать #ifdef, но когда я переключился на Doxygen для документации, я обнаружил, что закомментированные макросы не могут быть задокументированы (или, по крайней мере, Doxygen выдает предупреждение). Это означает, что я не могу документировать макросы переключения функций, которые в настоящее время не включены.

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

поэтому в этом случае лучше всегда определять макросы и использовать #if.

есть разница в случае другого способа указать условное определение водителю:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

выход:

344c344
< #define A 
---
> #define A 1

Это означает, что -DA - это синоним -DA=1 и если значение опущено, то это может привести к проблемам в случае #if A использование.

Comments

    Ничего не найдено.