Подтвердить что ты не робот

Оценка ленивого параметра

Я добавляю немного кода отслеживания и отладки в класс, который я рефакторинг.

У меня есть объект Trace, который имеет некоторые свойства и методы фильтрации bool CanTrace(Level, , TracePropertyList = no_additional_properties) и bool Trace(Level, string, TracePropertyList = no_additional_properties).

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

Повторение фрагмента кода

if(trace.CanTrace(LEVEL_INFO, some_props))
  trace.Trace(LEVEL_INFO, consume_time().to_str(), some_props);

является уродливым, и мне бы хотелось что-то короче.

Я думал о макросах

#define TRACE_WITH_PROPS(LEVEL,STRING,PROPS) //...

и

#define TRACE(LEVEL,STRING) //...

Есть ли лучший способ сделать это? Возможно с шаблонами или С++ 11? Мне не нравится скрывать вещи от компилятора с помощью define, и я прилагаю все усилия, чтобы удалить некоторые макросы в другом месте этой кодовой базы.

4b9b3361

Ответ 1

В С++ 11 вы можете использовать закрытие. У вас будет что-то вроде:

trace.Trace(LEVEL_INFO, [&](){ return format("x is %i") % ExpensiveWayToGetX(y, z); }, some_props);

В С++ 03 вы можете использовать boost.lambda hack для аналогичного эффекта.

Однако наличие макроса if(trace.CanTrace(...)) trace.Trace(...) все еще немного более эффективно, потому что оно даже не инициализирует объект со всеми ссылками, закрытие которых понадобится, если трассировка не включена. Я предлагаю комбинировать макрос с интерфейсом потока, поэтому у вас будет

#define TRACE(level, some_props) \
    if(!trace.CanTrace(level, some_props)) 0; \
        else trace.Trace(level, some_props)

и назовите его как

TRACE(LEVEL_INFO, some_props) << "x is " << ExpensiveWayToGetX(y, z);

или определите operator() вместо operator<< и для форматирования в формате printf:

TRACE(LEVEL_INFO, some_props)("x is %i", ExpensiveWayToGetX(y, z));

Если не включено, то 0, иначе на самом деле существует трассировка, чтобы он не ел другого, если вы когда-либо пишете:

if(IsSomethingWrong())
    TRACE(LEVEL_WARNING, some_props) << WhatIsWrong() << " is wrong";
else
    DoSomething();

(без остального в макросе, else после того, как он перейдет к if внутри макроса, но с этим другим он будет правильно разбираться)

Ответ 2

Чтобы избежать обработки строк, лямбды - хорошее решение, как в ответе Йоханнеса Шауба. Если ваш компилятор не поддерживает lambdas, тогда вы можете сделать это, как это, без функций С++ 0x и без макросов:

doTrace(LEVEL_INFO, some_props) << "Value of i is " << i;

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

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

Ответ 3

Я бы определенно поручился за то, что у вас есть определенный набор макросов за один и тот же код, появляющийся не раз. Просто определите набор макросов, имеющих несколько имен, с разными уровнями - и поверх них есть макрос, который будет иметь if-else, template magic, assertions, static-asserts, static-type-checking, больше macro-abuse и что нет.

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

Нарисуйте макросы тщательно!

Ответ 4

Насколько я знаю, это функции-шаблоны - лучшее место, чтобы убедиться, что компилятор пытается оптимизировать материал. Поэтому я хотел бы надеяться, что что-то вроде следующего может заставить компилятор оптимизировать создание message out, если вы создаете экземпляр Trace_<false> trace;

Это должно сделать трюк и дать вам идею:

template<bool yesno> struct Trace_ {};
template<> struct Trace_<false> {
    void operator()(const string &) const {}
};
template<> struct Trace_<true> {
   void operator()(const string &message) const {
      clog << message << endl;
   }
};

//Trace_<true> trace;
Trace_<false> trace;

int main() {
    trace("a message");
}

Как-то я думаю, что создание в экземпляре trace - не лучшая идея. Может быть, это можно сделать с помощью бесплатных функциональных шаблонов или статической функции-члена (operator() не может быть статическим)?

Ответ 5

(потому что этот ответ - совершенно другой подход, я отделяю его от другого, где я неправильно понял вопрос)

В время выполнения вы хотите предотвратить выполнение выражения с большой строкой-слиянием? Хм... сложно. Я думаю, вы могли бы использовать совершенную пересылку вариационных аргументов шаблона как примерную идею (псевдоним С++ 0x'ish):

template<typename MSGS...>
void trace(const MSGS... &&msgs) {
    if(IS_TRACE) {
        doTrace( std::forward<MSGS...>(msgs) );
    }
}

void func() {
    trace("A String", 52, image, matrix, "whatver doTrace can handle");
}

doTrace может быть рекурсивной функцией variadic-template (например, printf в Bjarne Stroutrups Часто задаваемые вопросы: Шаблоны Variadic). && и forward должны гарантировать, что аргументы не будут затронуты до тех пор, пока они не достигнут doTrace. Таким образом, вы должны иметь только один вызов с несколькими аргументами и if(IS_TRACE) -test, когда вы не хотите ничего отслеживать.

Если вам нравится эта идея, я запрограммирую ее - просто кричите!

Ответ 6

Это мое быстрое решение.

class Logger
{
public:
    Logger():m_lvl(0){;}
    void level(int i){m_lvl=i;}
    void log(int level, std::function<std::string(void)> fun)
    {
        if (level <= m_lvl)
        {
            std::cout << fun() << std::endl;
        }
    }
private:
    int m_lvl;
};
class Foo
{
public:
    Foo():m_a(3), m_b(5){;}
    void run()
    {
        Logger l;
        int c = m_b;
        std::cout << "Running" <<std::endl;
        auto lambda = [&](int my, int log)->std::string {
            std::cout <<"Consume while logging set to "<< log << " and my message level is "<< my << std::endl;
            return (std::string(this->m_a, 'a') + std::string(c, 'c'));
        };
        // The bind/lambda is just to show the different levels
        l.log(0, [=]{return lambda(0, 0);} );
        l.log(1, [=]{return lambda(1, 0);} );
        l.level(5);
        l.log(1, [=]{return lambda(1, 5);});
    }
private:
    int m_a, m_b;
};

int main()
{
    Foo f;
    f.run();
    return 0;
}

Выход

Запуск
Потребляйте при регистрации в 0 и мой уровень сообщений 0 aaaccccc
Потребляйте при регистрации до 5 и мой уровень сообщений 1
aaaccccc

Мы не тратили время на вывод/вычисление сообщения уровня 1 при установке на уровень журнала 0.