如何避免在日志语句中使用多个#ifdef

6
在我们的应用程序中,我们创建了自己的日志系统。在此日志系统中,有几种不同的日志类型,如调试、错误、警告、通信、性能等。代码中有很多#ifdef#endif来禁用特定的日志类型。这些#ifdef#endif使得代码难以阅读。
我们考虑删除这些#ifdef#endif,并在消息写入文件之前进行检查。这意味着会有很多“无用的”调用日志系统。这些调用不会导致任何写入活动。
是否有一种更好的方法来打开/关闭日志类型,而无需使用这些#ifdef#endif以及这些“无用的”调用?

你能举个例子说明你的意思吗?你是指每种日志类型只有一个ifdef,还是其他什么?也许展示一些ifdef的代码会有所帮助... - rabensky
3
你不能把所有的ifdef都放在一个地方,使用类似于“#ifdef ENABLE_WARN \n #define WARN(x) printf(x) \n #else \n #define WARN(x) \n #endif”的东西吗? - Oliver Charlesworth
@cluracan 我不是100%确定,但我猜测OP试图避免在各个地方都有大量的#ifdef DEBUG ... #endif - Dennis Meng
1
你可以使用一个常量全局日志级别和针对每种类型的单独函数(即,if(log_lvl < 3)等)来使优化器省略那些无用的调用。 - BlackBear
@DennisMeng 你的意思是他把ifdef放在代码里面?那么,只需在头文件中进行一次ifdef,定义一个“log_error/warning/communication”宏,根据日志级别执行无操作或记录即可。 - rabensky
2个回答

7
以下是关于这个问题的内容:
// comment out if not needed
#define ENABLE_LOG

#ifdef ENABLE_LOG
#  define LOG(x) x
#else
#  define LOG(x) (void) 0 
#endif

稍后您只需要调用:

LOG(mylogger.call());

根据Dietrich Epp的建议,更新了#else部分。


5
因为这样不会产生多余分号的编译器警告,我建议在else分支中使用define LOG(x) (void) 0 - Dietrich Epp
为什么我们需要 #else 部分? - 5YrsLaterDBA
1
如果您没有启用日志记录,那么对 LOG(mylogger.call()); 的调用将返回一个错误,指出 LOG 未定义。@5YrsLaterDBA - rabensky

7

除了非常有效的#define解决方案之外,我想介绍一种使用模板的替代方案。

template<bool B>
void log(std::string message){}

template<>
void log<true>(std::string message){log_internal(message);}

#define DEBUG true
#define COMMUNICATION false

...

log<DEBUG>("this message will be logged");
log<COMMUNICATION>("this message won't");

#定义解决方案对大多数情况确实很好,但有一些理由要使用这个解决方案:
  • 您可能需要作用域 - 即不让您的 log 设备污染全局命名空间。 这个解决方案可以放在 namespace 中,而 #define 无法。

  • 您可能需要更严格地控制可做和不可做的事情。 LOG(x) 宏定义的问题在于任何内容都可以放入 x - 如果关闭日志记录,您将看不到问题。

简而言之 - 您的代码可能会编译并工作:

LOG(std::cout << "Here!" << endl);

由于特定日志已关闭。但是两年后的某一天,有人会打开记录并在各个地方得到endl undefined错误。或者更糟糕的是-他可能会发现打开日志突然需要链接到长时间不存在的库(因为该日志专门调用了此库中定义的函数),或使用已经改变接口(甚至被完全删除!真实的故事:()的函数。

编辑

我被要求将以下内容添加到答案中:

在不记录日志的情况下似乎存在函数调用开销(空函数)。这并不是事实,因为编译器会将其优化掉。如果您想确保这一点-请向函数添加inline指令。

此外,您可能希望将其从std::string更改为const char *,以确保不会调用字符串构造函数-但编译器也应自动优化掉这一点。

无论如何,就像我说的那样,这并不比#define解决方案本质上更好。我实际上仍在我的项目中使用#define,但在某些特定情况下,此模板解决方案更可取。


即使实际函数什么也不做,使用这种方法在关闭时仍会调用日志功能吗?而使用 #define 方法,在关闭状态下编译时,LOG 行就好像从未存在过一样。 - OnlyJoe
@OnlyJoe 不会的。编译器会看到这是一个空函数,所以什么也不做。如果你真的想要,可以添加“inline”指令来确保。 - rabensky
@cluracan,你能在你的回答中添加那个inline指令吗? - 5YrsLaterDBA

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