Увеличить многословность кода С++ с помощью макросов - программирование
Подтвердить что ты не робот

Увеличить многословность кода С++ с помощью макросов

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

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

/* code */
#ifdef _DEBUG_
/* do debug operations here 
#endif

поэтому компиляция с -D_DEBUG_ должна делать трюк. без него эта часть не будет включена в мою программу.

Другим вариантом (по крайней мере для операций ввода-вывода) будет определение, по крайней мере, функции ввода-вывода, например

#ifdef _DEBUG_
#define LOG(x) std::clog << x << std::endl;
#else
#define LOG(x) 
#endif

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

4b9b3361

Ответ 1

Я предпочитаю использовать #ifdef с реальными функциями, чтобы функция имела пустое тело, если _DEBUG_ не определено:

void log(std::string x)
{
#ifdef _DEBUG_
  std::cout << x << std::endl;
#endif
}

Для этого предпочтения есть три большие причины:

  • Когда _DEBUG_ не определено, определение функции пуста, и любой современный компилятор полностью оптимизирует любой вызов этой функции (определение должно быть видимым внутри этой единицы перевода, конечно).
  • Защиту #ifdef следует применять только в небольшой локализованной области кода, а не при каждом вызове log.
  • Вам не нужно использовать много макросов, избегая загрязнения вашего кода.

Ответ 2

Вы можете использовать макросы для изменения реализации функции (как в решении sftrabbit). Таким образом, в вашем коде не останется пустых мест, и компилятор оптимизирует "пустые" вызовы.

Вы также можете использовать два разных файла для реализации отладки и выпуска, и пусть ваша IDE/build script выбирает подходящую; это вообще не имеет значения #defines. Просто запомните правило DRY и сделайте чистый код повторно используемым в сценарии отладки.

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

Ответ 3

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

Ответ 4

Для дополнительных проверок я полагался бы на assert (см. assert.h), который делает именно то, что вам нужно: проверьте, когда вы компилировать в debug, не проверяйте, когда скомпилированы для выпуска.

Для многословия более C + + версия того, что вы предлагаете, будет использовать простой класс Logger с логическим параметром шаблона. Но макрос прекрасно, если он хранится в классе Logger.

Ответ 5

Для коммерческого программного обеспечения, наличие отладочного вывода SOME, доступного во время выполнения на клиентских сайтах, обычно является ценной вещью. Я не говорю, что все должно быть скомпилировано в финальный двоичный файл, но совсем не странно, что клиенты делают что-то с вашим кодом, которого вы не ожидаете [или это заставляет код вести себя так, как вы этого не ожидаете ]. Возможность рассказать клиенту "Ну, если вы запустите myprog -v 2 -l logfile.txt и вы обычно делаете, тогда напишите мне logfile.txt", это очень и очень полезная вещь.

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

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

Ответ 6

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

#ifdef _DEBUG_
/* ... */
#endif

часть кода.

Ответ 7

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

#ifdef DEBUG
#define PR_DEBUG(fmt, ...) \
    PR_DEBUG(fmt, ...) printf("[DBG] %s: " fmt, __func__, ## __VA_ARGS__)
#else
#define PR_DEBUG(fmt, ...)
#endif

Использование:

#define DEBUG
<..>
ret = do_smth();
PR_DEBUG("some kind of code returned %d", ret);

Вывод:

[DBG] some_func: some kind of code returned 0

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

Ответ 8

По крайней мере, в юниверсе * Nix по умолчанию для этого типа используется NDEBUG (read no-debug). Если он определен, ваш код должен пропустить код отладки. То есть вы бы сделали что-то вроде этого:

#ifdef NDEBUG
inline void log(...) {}
#else
inline void log(...) { .... }
#endif

Ответ 9

Для меня это зависит от приложения к приложению.

У меня были приложения, в которых я хотел всегда регистрироваться (например, у нас было приложение, где в случае ошибок клиенты брали все журналы приложения и отправляли их нам для диагностики). В таком случае API протоколирования должен, вероятно, основываться на функциях (т.е. Не макросах) и всегда определяется.

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

В этом случае я предпочитаю однострочный макрос, подобный этому:

#ifdef NDEBUG
#define LOGSTREAM /##/
#else
#define LOGSTREAM std::clog
// or
// #define LOGSTREAM std::ofstream("output.log", std::ios::out|std::ios::app)
#endif

клиентский код:

LOG << "Initializing chipmunk feeding module ...\n";
//...
LOG << "Shutting down chipmunk feeding module ...\n";

Ответ 10

Это как и любая другая функция.

Мои предположения:

  • Нет глобальных переменных
  • Система, разработанная для интерфейсов

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

Это может быть журнал, или виджет, или менеджер памяти, например.

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