Короткое замыкание потока

Приложение имеет систему ведения журнала, которая позволяет включать или отключать функции ведения журнала своих модулей во время выполнения. Команды журнала принимают потоки на входе (что является безопасной альтернативой "sprintf", вряд ли будет более неприятная ситуация, чем если ваша система отладки является причиной сбоя.

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

 logger.Trace << "Requests pending:" << buffer.findRequests();

и findRequests() имеет высокую вычислительную сложность, даже если отключить уровень журнала трассировки для модуля, поиск будет выполняться (при сборке потока) до его отклонения внутри метода Trace operator<<.

Очевидной альтернативой было бы засорять код:

 if(logger.Trace.Enabled()) logger.Trace << ...

Это некрасиво и это не удобно. Я мог бы заменить его макросом, используя if, или тот, который использует короткое замыкание &&, будучи несколько более приятным (может использоваться как RValue, которое после потоковой философии возвращает bool false в отключенном потоке):

  #define TRACE    if(logger.Trace.Enabled()) logger.Trace

  #define TRACE    dummyLogVar = logger.Trace.Enabled() && logger.Trace

Ни один из них не является особенно красивым или безопасным. Сотрудник предложил закрыть:

  logger.Trace([&](f){f << "Requests pending:" << buffer.findRequests();});

.Trace будет оценивать закрытие только в том случае, если этот уровень включен. Логично, что приятно, но синтаксически абсолютно ужасно. Ввод этого беспорядка: logger.Trace([&](f){f <<... ;}); сотни раз?

Есть ли более аккуратный, безопасный и удобный способ предотвращения оценки потока?

Ответ 1

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

Вторая часть, убедившись, что тело макроса является одним из операторов, легко и обычно выполняется с помощью do { ... } while (false).

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

В своей простейшей форме, не беспокоясь об ограничении запятой, макрос мог бы выглядеть примерно так:

#define TRACE(output)                   \
    do                                  \
    {                                   \
        if (logger.Trace.Enabled())     \
        {                               \
            logger.Trace << output;     \
        }                               \
     } while (false)

Обратите внимание, что после while (false) не существует полушины.

Затем вы используете его как

TRACE("Requests pending:" << buffer.findRequests());

Часть do { ... } while (false) скорее всего будет оптимизирована компилятором, оставив вам простую проверку if. Если logger.Trace.Enabled() возвращает false, то кроме этой проверки ничего не должно произойти.

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

Используя переменные макросы, макрос будет выглядеть следующим образом:

#define TRACE(...)                       \
    do                                   \
    {                                    \
        if (logger.Trace.Enabled())      \
        {                                \
            logger.Trace << __VA_ARGS__; \
        }                                \
     } while (false)

Ответ 2

Я работал над этой проблемой. Finnaly, я создал класс Log с этим интерфейсом и связанными с ним макросами:

class Log
{
  public:
    static bool trace_is_active();
    static bool debug_is_active();
    static bool info_is_active();
    static bool warning_is_active();
    static bool error_is_active();
    static void write_as_trace(const std::string& msg);
    static void write_as_debug(const std::string& msg);
    static void write_as_info(const std::string& msg);
    static void write_as_warning(const std::string& msg);
    static void write_as_error(const std::string& msg);
};

#define LOG_TRACE(X) {if(Log::trace_is_active()){std::ostringstream o__;o__<<X;Log::write_as_trace(o__.str());}}
#define LOG_DEBUG(X) {if(Log::debug_is_active()){std::ostringstream o__;o__<<X;Log::write_as_debug(o__.str());}}
#define LOG_INFO(X) {if(Log::info_is_active()){std::ostringstream o__;o__<<X;Log::write_as_info(o__.str());}}
#define LOG_WARNING(X) {if(Log::warning_is_active()){std::ostringstream o__;o__<<X;Log::write_as_warning(o__.str());}}
#define LOG_ERROR(X) {if(Log::error_is_active()){std::ostringstream o__;o__<<X;Log::write_as_error(o__.str());}}

Тогда использование очень просто и ясно:

//...
LOG_WARNING("The variable x = " << x << " is out of range");
//...
LOG_DEBUG("The inverse of the matrix is inv(m) = " << std::endl << inv(m) << std::endl);

EDIT: Я отмечаю, что решение с do{...}while(false) лучше, потому что мое решение, за которым следует ;, является двумя инструкциями и не может использоваться в циклах или состоянии без записи между { и }. Теперь я могу улучшить свой код.: -)