我可以使用流操作符重写日志宏以使用C++模板函数吗?

Ogr*_*m33 8 c++ macros effective-c++

我们的项目使用宏来使单行语句中的日志记录变得简单和简单,如下所示:

DEBUG_LOG(TRACE_LOG_LEVEL, "The X value = " << x << ", pointer = " << *x);
Run Code Online (Sandbox Code Playgroud)

宏将第二个参数转换为stringstream参数,并将其发送到常规C++记录器.这在实践中非常有用,因为它使多参数记录语句非常简洁.然而,斯科特·迈尔斯说,在有效的C++第3版,(第2项)"您可以通过使用一个内联函数模板获得的宏所有的效率加上所有常规函数的可预测的行为和类型安全".我知道在C++中与可预测行为相关的宏用法有很多问题,所以我试图在代码库中尽可能多地消除宏.

我的日志宏定义类似于:

#define DEBUG_LOG(aLogLevel, aWhat) {  \
if (isEnabled(aLogLevel)) {            \
  std::stringstream outStr;            \
  outStr<< __FILE__ << "(" << __LINE__ << ") [" << getpid() << "] : " << aWhat;    \
  logger::log(aLogLevel, outStr.str());    \
}
Run Code Online (Sandbox Code Playgroud)

我已多次尝试将其重写为不使用宏的内容,包括:

inline void DEBUG_LOG(LogLevel aLogLevel, const std::stringstream& aWhat) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

和...

template<typename WhatT> inline void DEBUG_LOG(LogLevel aLogLevel, WhatT aWhat) {
    ...  }
Run Code Online (Sandbox Code Playgroud)

无济于事(以上两个重写都不会在第一个例子中编译我们的日志代码).还有其他想法吗?可以这样做吗?或者最好把它留作宏?

Xeo*_*Xeo 6

记录保留在少数几个地方是你不能完全废除宏的一个,因为你需要调用点信息(__LINE__,__FILE__不可用,否则......).另见这个问题.

但是,您可以将日志记录逻辑移动到单独的函数(或对象)中,并通过宏提供调用站点信息.你甚至不需要模板功能.

#define DEBUG_LOG(Level, What) \
  isEnabled(Level) && scoped_logger(Level, __FILE__, __LINE__).stream() << What
Run Code Online (Sandbox Code Playgroud)

有了这个,用法保持不变,这可能是一个好主意,因此您不必更改代码的负载.使用&&,您将获得与您的if子句相同的短行为.

现在,scoped_logger它将是一个RAII对象,它将实际记录它在被破坏时获得的内容,也就是在析构函数中.

struct scoped_logger
{
  scoped_logger(LogLevel level, char const* file, unsigned line)
    : _level(level)
  { _ss << file << "(" << line << ") [" << getpid() << "] : "; }

  std::stringstream& stream(){ return _ss; }
  ~scoped_logger(){ logger::log(_level, _ss.str()); }
private:
  std::stringstream _ss;
  LogLevel _level;
};
Run Code Online (Sandbox Code Playgroud)

公开底层std::stringstream对象使我们不得不编写自己的operator<<重载(这将是愚蠢的).通过函数实际暴露它的需要很重要; 如果scoped_logger对象是临时的(rvalue),那么std::stringstream成员也是如此,operator<<如果我们不以某种方式将其转换为左值(引用),则将找到唯一的成员重载.您可以在此处阅读有关此问题的更多信息(请注意,此问题已在使用rvalue流插入器的C++ 11中得到修复).这种"转换"是通过调用一个简单地返回流的常规引用的成员函数来完成的.

Ideone上的小型实例.


小智 5

不,由于您在宏中使用运算符(<<)而无法将此精确宏重写为模板,因此无法将其作为模板参数或函数参数传递.

我们遇到了同样的问题,并使用类似的语法通过基于类的方法解决了它

DEBUG_LOG(TRACE_LOG_LEVEL) << "The X value = " << x << ", pointer = " << *x << logger::flush;
Run Code Online (Sandbox Code Playgroud)

这确实需要重写代码(通过使用正则表达式)并引入一些类魔术,但是提供了更大灵活性(延迟输出,每个日志级别的输出选项(到文件或标准输出)和类似的东西)的额外好处.