#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
}

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

Ответ 1

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

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

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

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

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

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

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

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

#define DEBUG_ENABLED true

вместо

#define DEBUG_ENABLED (1)

Ответ 2

Они оба отвратительны. Вместо этого сделайте следующее:
#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

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

Ответ 3

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

#define FOO 0

затем

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

Ответ 4

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

Если у вас есть feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

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

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

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

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

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

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

Ответ 7

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

#if defined(DEBUG_ENABLED)

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

#if !defined(DEBUG_ENABLED)

против.

#ifndef(DEBUG_ENABLED)

Ответ 8

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

#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);

Ответ 9

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

Ответ 10

#if и #define MY_MACRO (0)

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

Например:

#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 ===|

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

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

#ifdef и #define MY_MACRO

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

#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 ===|

Итак...

Заключение

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

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

Post Scriptum

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

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

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

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

Интересно. : -)

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

#ifdef macro

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

#if macro

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

#if macro !=0

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

#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 

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

Ответ 15

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

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

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

Ответ 16

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

Вот что С++ Gotchas от Stephen C. Dewhurst говорит об использовании # if.

Ответ 17

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

Ответ 18

Существует различие в случае другого способа указания условного определения для драйвера:

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

выход:

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

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

Ответ 19

Мне нравится #define DEBUG_ENABLED (0), когда вам может потребоваться несколько уровней отладки. Например:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

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

Также #ifndef вокруг определения облегчает выбор определенного уровня отладки в командной строке:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Если бы не это, я бы дал преимущество #ifdef, потому что флаг компилятора /make был бы переопределен флагом в файле. Так что вам не нужно беспокоиться об изменении заголовка перед выполнением коммита.

Ответ 20

Если вы используете библиотеку boost, особенно boost-1_57\boost\move\unique_ptr.hpp, #define D() может быть перегружен.