开发者

Lazy Parameter Evaluation

I'm adding a bit amount of tracing and debugging code into a class that I'm refactoring.

I have a Trace object which has some filtering properties and methods bool CanTrace(Level, , TracePropertyList = no_additional_properties) and bool Trace(Level, string, TracePropertyList = no_additional_properties).

There are already many places in the code where this trace object is used, and the string argument to the Trace method is usually some expression that I would like to avoid evaluating if I'm not going to end up outputting tracing info.

Repeating the chunk of code

if(trace.CanTrace(LEVEL_INFO, som开发者_开发技巧e_props))
  trace.Trace(LEVEL_INFO, consume_time().to_str(), some_props);

is ugly, and I'd like something shorter.

I was thinking about the macros

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

and

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

Is there a better way to do this? Possibly with templates or C++11? I don't like hiding things from the compiler with defines, and I'm doing my best to remove some macros elsewhere in this codebase.


In C++11, you can use closure. You'd have something like:

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

In C++03, you can use the boost.lambda hack for similar effect.

However, having a macro wrapping the if(trace.CanTrace(...)) trace.Trace(...) is still slightly more efficient, because it doesn't even initialize the object with all the references the closure will need if tracing is not on. I suggest combining the macro with the stream interface, so you'd have

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

and call it like

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

or define operator() instead of operator<< and for printf-style formatting:

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

The if not enabled, than 0, else actually trace is there so that it does not eat the else if you ever write:

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

(without else in the macro, the else after it would go to the if inside the macro, but with that else, it will parse correctly)


To avoid string processing, lambdas are a good solution as in the answer by Johannes Schaub. If your compiler doesn't support lambdas yet then you could do it like this with no C++0x features and no macroes:

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

doTrace would return a temporary object with a virtual operator<<. If no tracing is to be performed, then return an object whose operator<< does nothing. Otherwise return an object whose operator<< does what you want. The destructor of the temporary object can signal that the string being outputted is done. Now destructors shouldn't throw, so if the final processing of the trace event can throw an exception, then you may need to include an end-of-event overload of operator<<

This solution causes several virtual function calls even when no tracing is done, so it is less efficient than "if(tracing) ...". That should only really matter in performance critical loops where you probably want to avoid doing tracing anyway. In any case you could revert to checking tracing with an if in those cases or when the logic for doing the tracing is more complicated than comfortably fits in a sequence of <<'s.


I would definitely vouch for having well defined set of macros over same code appearing umpteen times. Just define set of macros having multiple names, with different levels - and on top of them have a macro that will have if-else, template magic, assertions, static-asserts, static-type-checking, more of macro-abuse and what not.

Using macros would also allow you to conditionally include/exclude some part (like exclusion of static-asserts, asserts etc). Initially it would be hard to write up macros, adapt to them, find compiler/runtime bugs related with them, but they will swoosh away with the feature-rich facilities they would provide in later stages of coding, bug-detection and development.

Draw macros carefully!


(because this answer is a complete different approach, I separate it from my other one, where I misunderstood the question)

At runtime you want to prevent the a big-string-merge expression is being executed? Hmm... difficult. I think you could use perfect forwarding of variadic template arguments just as a rough idea (C++0x'ish pseudocode):

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 can then be a recursive variadic-template function (like the printf in Bjarne Stroutrups FAQs:Variadic Templates). The && and forward should ensure that the arguments are not touched until they arrive at doTrace. So, you should have only one call with a couple of arguments and the if(IS_TRACE)-test when you don't want to trace anything.

If you like the idea, I will program it out -- just holler!


As far as I know is are template-functions the best place to ensure that the compiler tries to optimize stuff away. So, I would hope that something like the following could make the compiler optimize the creation of the message out, if you instantiate Trace_<false> trace;

This should do the trick, and give you the idea:

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");
}

Somehow I think create in instance trace is not the best idea. Maybe one can to this with free function-templates only, or a static member function (operator() can not be static)?


This is my quick solution.

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;
}

Output

Running
Consume while logging set to 0 and my message level is 0
aaaccccc
Consume while logging set to 5 and my message level is 1
aaaccccc

We didn't consume time outputting/computing a level 1 message when set to log level 0.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜