我正在将一些跟踪和调试代码添加到我正在重构的类中。
我有一个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?我不喜欢用编译器隐藏编译器中的内容,我正在尽力删除此代码库中其他地方的一些宏。
答案 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-else
,template magic
,assertions
,{ {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消息的时间。