Пользовательский спецификатор времени компиляции для отладки / игнорирования функций - PullRequest
0 голосов
/ 06 марта 2020

Каковы хорошие способы реализации следующих функций отладки? Я хотел бы определить простой в использовании спецификатор функции , аналогичный встроенному, который сообщает компилятору , когда следует полностью игнорировать функцию на основе включенных флагов препроцессора. В идеале я хочу объявить такие функции:

DEBUG_FUNCTION_VERBOSE void saveMesh(const std::string &fileName, const Mesh &mesh, ...);
DEBUG_FUNCTION_INFO void logInfo(const std::string &message);
DEBUG_FUNCTION_CHECK void checkVertexAttributes(const Mesh &mesh);
DEBUG_FUNCTION_ERROR void logError(const std::string &messageOfDoom);

Существует порядок от менее важных и медленных до критических и более быстрых. Идея состоит в том, чтобы я установил флаг препроцессора, который определяет, какие из этих специальных функций отладки включены. Например, может существовать флаг типа DEBUG_LEVEL_CHECK, который включает последние два типа функций отладки, или другой флаг DEBUG_LEVEL_VERBOSE, который включает все из них. Если тип функции отладки отключен, я, по сути, хочу, чтобы компилятор игнорировал все вызовы этой функции, включая оценку ее аргументов (например, assert в режиме выпуска).

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

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

#ifdef DEBUG_LEVEL_CHECK
checkVertexAttributes(mesh);
#endif // DEBUG_LEVEL_CHECK

Я просто хочу вызвать функцию нормально:

checkVertexAttributes(mesh);

В идеале реализация этой схемы отладки должна иметь следующие свойства: во-первых, в коде должна быть только одна точка для каждой функции, где я должен объявить ее уровень отладки (может быть похоже на добавление спецификатора, такого как ключевое слово inline). Во-вторых, я хочу определить уровень отладки logi c только один раз, поскольку он всегда один и тот же. В-третьих, эти функции отладки имеют произвольный контекст (принадлежат некоторому классу, некоторому пространству имен или ...), и я мог бы захотеть добавить такие функции позже. Это означает, что функции являются функциями отладки и не должны зависеть от реализации самого уровня отладки logi c. Я не хочу раздувать эту логику c с перечислением всех используемых функций отладки в этом месте. В-четвертых, если функция отключена, следует пропустить не только ее выполнение, но и оценку ее аргументов (например, для assert). Наконец, я хочу удобно вызывать только функции отладки, зависящие от этой логики c, но прозрачным образом, как показано выше.

Я видел этот пост: Что именно происходит с пустыми встроенными функциями? Но это скорее похоже на то, что в моем случае это приведет к избыточному коду, и я не уверен, как вы примените это к перегруженные функции с одинаковыми именами.

Ответы [ 3 ]

0 голосов
/ 06 марта 2020

Вы можете заставить компилятор глотать любые вызовы функций с помощью макроса variadi c.

void test(int, int) {}
void test(int) {}

#define test(...)

int main()
{
    test(78, 12);
    test(12);
    test(swallows literally anything);
}

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

#ifdef DEBUG_FUNCTION_VERBOSE
#define DEBUG_FUNCTION_INFO
#else
#define saveMesh(...)
#endif

#ifdef DEBUG_FUNCTION_INFO
#define DEBUG_FUNCTION_CHECK
#else
#define logInfo(...)
#endif

#ifdef DEBUG_FUNCTION_CHECK
#define DEBUG_FUNCTION_ERROR
#else
#define checkVertexAttributes(...)
#endif

#ifndef DEBUG_FUNCTION_ERROR
#define logError(...)
#endif
0 голосов
/ 07 марта 2020

Я скомпрометировал некоторые требования и придумал следующее:

#include <functional>

enum class Debug
{
    Verbose = 0,
    Info = 1,
    Check = 2,
    Error = 3
};

#define DebugLevelApp Debug::Verbose


template <Debug functionLevel, typename Function, typename... Args>
inline void debugCall(Function&& func, Args&&... args)
{
    if constexpr (DebugLevelApp <= functionLevel)
    {
        auto function = std::bind(std::forward<Function>(func), std::forward<Args>(args)...);
        function();
    }
}

int main()
{
    debugCall<Debug::Verbose>(debug1, sideEffect());
    debugCall<Debug::Verbose>(debug2, sideEffect(), sideEffect());
    debugCall<Debug::Info>(debug2, sideEffect(), sideEffect());
    return 0;
}

Недостатком здесь является то, что все аргументы оцениваются, даже если соответствующая функция не вызывается. Это выглядит как нежелательное неожиданное поведение.

Поэтому я изменил реализацию схемы отладки так:

enum class Debug
{
    Verbose = 0,
    Info = 1,
    Check = 2,
    Error = 3
};

#ifndef DEBUG_LEVEL_APP
    #define DEBUG_LEVEL_APP Debug::Info
#endif // DEBUG_LEVEL_APP

#define DEBUG_CALL(CALL_DEBUG_LEVEL) if constexpr (APP_DEBUG_LEVEL <= CALL_DEBUG_LEVEL)

Если я хочу иметь вызовы функций отладки, которые я могу легко исключить, используя флаг препроцессора DEBUG_LEVEL_APP Я отмечаю такие вызовы следующим образом:

DEBUG_CALL(Debug::Verbose)
debug2(sideEffect(), sideEffect());
DEBUG_CALL(Debug::Info)
debug2(sideEffect(), sideEffect());
DEBUG_CALL(Debug::Info)
debug1(sideEffect());

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

0 голосов
/ 06 марта 2020

Что-то вроде:

inline void saveMesh(...) {
    #ifdef DEBUG
    ...
    #else
    return;
 }

Если DEBUG не определено, любые вызовы этой функции ничего не сделают, и компилятор может полностью оптимизировать это. Это то, что вы ищете?

...