懒惰参数评估

时间:2011-07-15 18:39:14

标签: c++ c++11

我正在将一些跟踪和调试代码添加到我正在重构的类中。

我有一个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 个答案:

答案 0 :(得分:6)

在C ++ 11中,您可以使用闭包。你有类似的东西:

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

在C ++ 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));

if 启用,而不是0,否则实际上跟踪是存在的,所以如果你写过它就不会吃掉其他内容:

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

(在宏中没有其他内容,else之后会转到宏内部的if,但是除此之外,它会正确解析)

答案 1 :(得分:0)

为了避免字符串处理,lambda是一个很好的解决方案,如Johannes Schaub的回答。如果您的编译器还不支持lambdas,那么您可以这样做,没有C ++ 0x功能且没有宏:

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

doTrace会返回一个带有虚拟运算符&lt;&lt;的临时对象。如果不执行跟踪,则返回其运算符&lt;&lt;什么也没做。否则返回一个运算符&lt;&lt;做你想要的。临时对象的析构函数可以发信号通知正在输出的字符串已完成。现在析构函数不应该抛出,因此如果跟踪事件的最终处理可能抛出异常,那么您可能需要包含运算符的结束事件重载&lt;&lt;

即使没有跟踪,此解决方案也会导致多个虚函数调用,因此效率低于“if(tracing)...”。这应该只在性能关键循环中真正重要,你可能想要避免进行跟踪。在任何情况下,您都可以在这些情

答案 2 :(得分:0)

我肯定会保证在相同的代码上出现非常明确的宏集。只需定义一组具有多个名称的宏,具有不同的级别 - 并且在它们之上有一个宏将具有if-elsetemplate magicassertions,{ {1}},static-asserts,更多static-type-checking以及不是macro-abuse

使用宏还允许您有条件地包含/排除某些部分(例如排除静态断言,断言等)。最初很难编写宏,适应它们,找到与它们相关的编译器/运行时错误,但它们将在编码,错误检测和开发的后期阶段提供功能丰富的功能。 / p>

仔细绘制宏!

答案 3 :(得分:0)

据我所知,模板函数是确保编译器尝试优化内容的最佳位置。所以,如果你实例化message

,我希望像下面这样的东西能让编译器优化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()不能是静态的)才可以这样做?

答案 4 :(得分:0)

(因为这个答案是完全不同的方法,我把它与我的另一个方法分开,我误解了这个问题)

运行时,您想要阻止正在执行大字符串合并表达式吗?嗯...很难。我认为你可以使用完美转发可变参数模板参数,就像一个粗略的想法(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可以是递归可变参数模板函数(与Bjarne Stroutrups FAQs:Variadic Templates中的printf一样)。 &&forward应确保在到达doTrace之前不会触及参数。所以,当你不想跟踪任何东西时,你应该只有一个带有几个参数的调用和if(IS_TRACE) - 测试。

如果你喜欢这个主意,我会把它编程出来 - 只是大喊大叫!

答案 5 :(得分:0)

这是我的快速解决方案。

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

当设置为记录级别0时,我们没有消耗输出/计算级别1消息的时间。