Ленивая оценка параметров - PullRequest
12 голосов
/ 15 июля 2011

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

У меня есть объект 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) //...

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

Ответы [ 6 ]

6 голосов
/ 15 сентября 2011

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

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

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

Однако, имея макрос, обертывающий 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 внутри макроса, но с этим else, оно будет правильно проанализировано)

0 голосов
/ 15 сентября 2011

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

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.

0 голосов
/ 15 сентября 2011

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

В время выполнения вы хотите предотвратить большую строку-мердж выражение выполняется?Хм ... сложно.Я думаю, вы могли бы использовать совершенную пересылку аргументов шаблона переменной просто как грубую идею (псевдокод C ++ 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 в Часто задаваемые вопросы о Бьярне Струпупах: Variadic Templates ).&& и forward должны гарантировать, что аргументы не будут затронуты, пока они не достигнут doTrace.Итак, у вас должен быть только один вызов с парой аргументов и if(IS_TRACE) -тест, когда вы не хотите ничего отслеживать.

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

0 голосов
/ 14 сентября 2011

Насколько я знаю, функции шаблонов - лучшее место, чтобы компилятор пытался оптимизировать вещи. Итак, я надеюсь, что что-то вроде следующего может заставить компилятор оптимизировать создание 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() не может быть статической)?

0 голосов
/ 16 июля 2011

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

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

Рисуй макросы осторожно!

0 голосов
/ 16 июля 2011

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

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

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

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

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