C++中的懒惰日志记录

8
假设我们有几个日志级别:trace、debug、info、error。我想知道是否有一种方法可以编写以下代码:
enum log_level = {trace, debug, info, error};

log_level global_log_level = info;

void log(log_level level, string& message){
    if (level >= global_log_level){
        std::cout << message << std::endl;
    }
}

string create_message(){
    ...
}

log_level level = debug;
log (level, create_message());

如果级别小于全局严重性级别,则不会调用create_message。确实,create_message可能会很长,并且无论如何它都会创建一个字符串。如果有很多“debug”日志,在非调试模式下运行时,这些日志可能会成为相当大的开销。
我知道如果函数“log”是宏,则可以这样做,仅在severity > minimal_severity时调用create_message(); 但是没有宏还有没有其他方法可以做到这一点?
编辑:
在上述内容中,我没有指定create_message,因为它可以是任何东西,特别是:
log(level, "Created object " + my_object.getName());

在这种情况下,有没有一种方法可以以相对透明的方式为调用日志的程序员编写日志,而不创建完整的字符串?
感谢您的阅读。

你可以让 create_message() 检查 global_log_level - NPE
1
你能否将一个名为 create_message 的函数传递给 log(),这样当级别适当时,log() 只会构建昂贵的消息? - JaredC
你好,我提到了一个未指定的函数create_message(),因为消息可能来自任何地方,但它可以即时编写,比如log(level, "Object " + my_object.getName() + " has been created"); - 在这种情况下,我无法向create_message()传递任何内容,它本质上是2个字符串之间的operator+...而且我也不能将其作为一个函数传递给log... - GHL
4个回答

6
有几种选择。一个有趣的选择是将`create_message`作为一个`std::function`传递,并从`log`内部调用它:
void log(log_level level, std::function<std::string()> message_creator){
    if (level >= global_log_level){
        std::cout << message_creator() << std::endl;
    }
}

那么您将这样调用它:
log(level, create_message);

如果你将任意表达式用lambda包装起来,它就可以作为参数使用:

log(level, [&](){ return "Created object " + my_object.getName(); });

如果您真的不希望参数被计算(如您在评论中所描述的),那么您需要在调用之外检查级别:
if (level >= global_log_level) {
  log(level, create_message());
}

[]替换为[&]。如果create_message是任意带有一些参数的函数,那该怎么办?仅延迟创建消息的函数似乎是不够的;推迟函数参数本身的评估也似乎是合理的。log函数如何知道这些参数是另一个问题。 - Nawaz
3
log 设为模板,编译器就可以内联 lambda 表达式。 - ipc
@Nawaz:在这种情况下,您可以将调用包装到零参数 Lambda 中。 - Yakov Galka
嗨,sftrabbit,非常感谢您的回答;我还不熟悉std :: function和lambda,但这似乎可以实现我想要的! - GHL
@ipc,你能详细说明一下吗? - GHL
2
Lambda是一种轻量级类型,具有自己的operator()std::function是一种复杂的类型擦除机制,创建和调用都很昂贵。如果log接受一个lambda对象,则可以省略创建(因为编译器知道它是无状态的),并且可以内联operator(),从而获得全速度。这对于stdlib中的许多算法也很有用。 - ipc

6
与@sftrabbit类似,但可以采用@ipc建议的模板方式。这种方式避免了std :: function机制,而且编译器有望将其内联优化,因此运行速度有望更快。
template< typename F >
void log(log_level level, F message_creator){
    if (level >= global_log_level){
        std::cout << message_creator() << std::endl;
    }
}

+1 是因为我没有时间修改我的答案:D - Joseph Mansfield

1

优先选择@sftrabbit的答案。但是如果您不想更改log(),可以调用它:

log (level, (level >= global_log_level)? create_message() : "");

确实,这个方法可行,我没有想到使用三元运算符...但是如果调用log的人不知道记录器的内部情况以及全局变量global_log_level的存在呢? - GHL
@GHL 然后你将这个结构转换成宏。 有点循环,呵呵;-) - paddy
是的,确实可以 :) - GHL

1
你可以创建一个宏。


    #define log(level, message) { \
    if(level >= global_log_level) {\
    cout << message; }}

Now if you call

log(debug, create_message());
create_message will be called only if debug level is the desired one.


只是好奇,为什么宏对你来说不是一个好的选择? - strannik
1
在我看来,这是C++11之前最好的解决方案。 - ipc
参数传递开销怎么样?我喜欢宏,因为它们很便宜。这仍然应该比C++ 11的方式更快。即使在C++ 11之前,您仍然可以传递函数指针,但对我来说宏更好。 - strannik
我只是担心宏的可能副作用...而且我发现它们很快变得难以理解和调试。但说实话,宏是C++中我还没有多少使用经验的组成部分。 - GHL
你可能没有使用过WTL :) 宏并不是邪恶的 ;) - strannik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接