调试日志语句的预处理器宏在C++中有一席之地吗



最近我一直在阅读Scott Meyers的Effective C++Second Edition,以改进C++最佳实践。他列出的其中一项鼓励C++程序员避免使用预处理器宏,而"更喜欢编译器"。他甚至说,除了#include和#ifdef/#ifndef之外,C++中几乎并没有使用宏的原因。

我同意他的推理,因为你可以完成以下宏

#define min(a,b) ((a) < (b) ? (a) : (b))

具有以下C++语言功能的

template<class T>
inline const T & min(const T & a, const T & b) {
    return a < b ? a : b;
}

其中inline为编译器提供了删除函数调用并插入内联代码和模板的选项,内联代码和模版可以处理具有重载或内置>运算符的多个数据类型。

EDIT--如果a和b的数据类型不同,则此模板声明将不完全匹配所声明的宏。请参阅Pete的评论以获取示例。

然而,我很想知道在C++中使用宏进行调试日志记录是否有效。如果我下面介绍的方法不是一个好的实践,有人会建议另一种方法吗?

去年,我一直在Objective-C中进行编码,我最喜欢的2D引擎之一(cocos2d)使用宏创建日志语句。宏如下:

/*

* if COCOS2D_DEBUG is not defined, or if it is 0 then
 *  all CCLOGXXX macros will be disabled
 *
 * if COCOS2D_DEBUG==1 then:
 *      CCLOG() will be enabled
 *      CCLOGERROR() will be enabled
 *      CCLOGINFO() will be disabled
 *
 * if COCOS2D_DEBUG==2 or higher then:
 *      CCLOG() will be enabled
 *      CCLOGERROR() will be enabled
 *      CCLOGINFO() will be enabled
 */

#define __CCLOGWITHFUNCTION(s, ...) 
NSLog(@"%s : %@",__FUNCTION__,[NSString stringWithFormat:(s), ##__VA_ARGS__])
#define __CCLOG(s, ...) 
NSLog(@"%@",[NSString stringWithFormat:(s), ##__VA_ARGS__])

#if !defined(COCOS2D_DEBUG) || COCOS2D_DEBUG == 0
#define CCLOG(...) do {} while (0)
#define CCLOGWARN(...) do {} while (0)
#define CCLOGINFO(...) do {} while (0)
#elif COCOS2D_DEBUG == 1
#define CCLOG(...) __CCLOG(__VA_ARGS__)
#define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__)
#define CCLOGINFO(...) do {} while (0)
#elif COCOS2D_DEBUG > 1
#define CCLOG(...) __CCLOG(__VA_ARGS__)
#define CCLOGWARN(...) __CCLOGWITHFUNCTION(__VA_ARGS__)
#define CCLOGINFO(...) __CCLOG(__VA_ARGS__)
#endif // COCOS2D_DEBUG

这个宏提供了一个令人难以置信的实用程序,我想把它合并到我的C++程序中。编写有用的日志语句就像一样简单

CCLOG(@"Error in x due to y");

更好的是,如果COCOS2D_DEBUG设置为0,那么这些语句就永远不会重见天日。检查条件语句以查看是否应该使用日志记录语句没有开销。这在从开发过渡到生产时很方便。如何在C++中重现同样的效果?

那么,这种类型的宏属于C++程序吗?有没有更好的、更C++的方法可以做到这一点?

首先,Scott的语句是在宏由于历史原因,使用过度。尽管如此一般来说,在少数情况下,宏是有意义的。其中日志记录,因为只有宏可以自动插入CCD_ 1和CCD_。此外,只有宏可以决心什么都不做(尽管根据我的经验,这没什么大不了的)。

像您所展示的宏在C++中并不常见。有日志记录的两种常见变体:

#define LOG( message ) ... << message ...

其允许形式为" x = " << x的消息,并且可以通过重新定义宏和完全抑制

#define LOG() logFile( __FILE__, __LINE__ )

其中logFile返回std::ostream的包装定义operator<<,并允许以下内容:

LOG() << "x = " << x;

这样做,LOG()右侧的所有表达式将始终进行评估,但操作正确,不会设置格式除非日志处于活动状态,否则将执行。

使用宏有"正确"的地方,也有不好的地方。在函数工作的地方使用宏是个坏主意。在我的书中,使用函数不做同样事情的宏是非常好的。

我经常使用这样的结构:

#defien my_assert(x) do { if (!x) assert_failed(x, #x, __FILE__, __LINE__); } while(0)
template<typename T> 
void assert_failed(T x, const char *x_str, const char *file, int line)
{
   std::cerr << "Assertion failed: " << x_str << "(" << x << ") at " << file << ":" << line << std::endl;
   std::terminate();
}

使用字符串化"运算符"的另一个技巧是这样的:

enum E
{
   a, 
   b, 
   c,
   d
 };
 struct enum_string
 {
    E v;
    const char *str;
 };
 #define TO_STR(x) { x, #x }
 enum_string enum_to_str[] = 
 {
    TO_STR(a),
    TO_STR(b),
    TO_STR(c),
    TO_STR(d),
  };

节省了很多重复的东西。。。

所以,是的,它在某些情况下是有用的。

最新更新