我的大多数类都有调试变量,这使它们看起来像这样:
class A
{
// stuff
#ifndef NDEBUG
int check = 0;
#endif
};
和方法可能如下所示:
for (/* big loop */) {
// code
#ifndef NDEBUG
check += x;
#endif
}
assert(check == 100);
几乎没有比#ifndef NDEBUG所有的东西更丑的东西。不幸的是,我知道没有编译器可以在没有这些#ifndefs的情况下优化 check 变量(我不知道是否允许这样做)。
所以我试图提出一个让我的生活更轻松的解决方案。以下是它现在的样子:
#ifndef NDEBUG
#define DEBUG_VAR(T) T
#else
template <typename T>
struct nullclass {
inline void operator+=(const T&) const {}
inline const nullclass<T>& operator+(const T&) const { return *this; }
// more no-op operators...
};
#define DEBUG_VAR(T) nullclass<T>
#endif
因此在调试模式下,DEBUG_VAR(T)只生成一个T.否则它会生成一个只有no-ops的“null类”。我的代码看起来像这样:
class A {
// stuff
DEBUG_VAR(int) check;
};
然后我可以使用检查,就像它是一个普通变量一样!真棒!但是,仍有两个问题我无法解决:
“null class”没有push_back()等没什么大不了的。无论如何,大多数调试变量都是整数。
C ++中的每个类至少有1个字符宽。因此,即使在发布模式下,使用N个调试变量的类至少会有N个字符太大。这在我看来是不可接受的。它违背了零开销原则,我尽可能地瞄准它。
那么,如何解决第二个问题?甚至可以摆脱#ifndef NDEBUG而不会在非调试模式下损害性能?我接受任何好的解决方案,即使它是你最黑暗的C ++魔法或C ++ 0x。
答案 0 :(得分:8)
你无法解决第二个问题,因为c ++标准要求类或对象的sizeof至少为一个字节。
最简单的解决方案是不要引入这样的黑客攻击,并对代码进行适当的单元测试。
答案 1 :(得分:8)
怎么样:
#ifndef NDEBUG
#define DEBUG_VAR(T) static nullclass<T>
#endif
现在没有额外的存储空间被添加到使用DEBUG_VAR(T)
作为成员的类中,但声明的标识符仍然可以像使用成员一样使用。
答案 2 :(得分:5)
这样的事情可行:
#ifdef NDEBUG
#define DEBUG_VAR(type, name)
#define DEBUG_VAR_OP(code)
#else
#define DEBUG_VAR(type, name) type name;
#define DEBUG_VAR_OP(code) code;
#endif
用法示例:
struct Foo
{
DEBUG_VAR(int, count)
};
void bar(Foo* f)
{
DEBUG_VAR_OP(f->count = 45)
}
但是,请注意,一般来说,在程序的不同配置之间存储器布局方面存在的差异越大,越多的硬错误(“它在调试中工作,但在发布中随机崩溃”)你将会得到。因此,如果您经常使用其他调试数据,则应重新设计数据结构。当有大量调试数据时,更喜欢在发布模式下留下指向调试数据的指针(即struct Foo { ... ; struct FooDebug* debugData; /* NULL in Release */ };
)
答案 3 :(得分:0)
如何在调试模式下声明成员对象静态:
#define DEBUG_VAR(T) static nullclass<T>
您必须在某处定义每个对象的实例。
(顺便说一下,空类的对象必须占用空间的原因是它们可以具有唯一的指针。)
编辑:删除了第二个想法 - 将无效。