在Release版本中使用assert()时避免未使用的variables警告

有时候,局部variables只用于在assert()中检查它的目的,

int Result = Func(); assert( Result == 1 ); 

在Release版本中编译代码时,通常会禁用assert(),所以这段代码可能会产生一个关于Result被设置但不会被读取的警告。

一个可能的解决方法是 –

 int Result = Func(); if ( Result == 1 ) { assert( 0 ); } 

但它需要太多的打字,不容易在眼睛上,并导致条件总是检查(是的,编译器可能会优化检查,但仍然)。

我正在寻找一种替代方式来expression这个assert()的方式,不会导致警告,但仍然很容易使用,并避免改变assert()的语义。

(在这个区域的代码中禁止使用#pragma的警告不是一个选项,并降低警告级别使其消失是不是一个选项…)。

我们使用macros来具体指明何时未使用的东西:

 #define _unused(x) ((void)(x)) 

那么在你的例子中,你会有:

 int Result = Func(); assert( Result == 1 ); _unused( Result ); // make production build happy 

这样(a)生产build立成功,(b)在代码中显而易见的是variables不被devise使用 ,而不是被遗忘。 当不使用函数的参数时,这是特别有用的。

我不能给出比这更好的答案,解决这个问题,还有更多:

愚蠢的C + +技巧:在断言的冒险

 #ifdef NDEBUG #define ASSERT(x) do { (void)sizeof(x);} while (0) #else #include <assert.h> #define ASSERT(x) assert(x) #endif 
 int Result = Func(); assert( Result == 1 ); 

这种情况意味着在发布模式下,你真的想要:

 Func(); 

但是Func是非空的,即它返回一个结果,即它是一个查询

据推测,除了返回一个结果, Func修改了一些东西(否则,为什么麻烦调用它,而不是使用它的结果?),即它是一个命令

通过命令 – 查询分离原则 (1), Func不应该同时是命令和查询。 换句话说,查询不应该有副作用,并且命令的“结果”应该用对象状态的可用查询来表示。

 Cloth c; c.Wash(); // Wash is void assert(c.IsClean()); 

比…更好

 Cloth c; bool is_clean = c.Wash(); // Wash returns a bool assert(is_clean); 

前者不会给你任何警告,后者会这样做。

所以,总之,我的答案是:不要写这样的代码:)

更新(1):您要求提供关于命令 – 查询分离原则的参考 。 维基百科是相当丰富的。 我在Bertrand Meyer的第二版“面向对象软件构造”中阅读了这个devise技术。

更新(2): j_random_hacker评论“OTOH,每个”命令“函数f(),以前返回一个值,现在必须设置一些variableslast_call_to_f_succeeded或类似”。 这只适用于不承诺任何合同内容的function,即可能“成功”的function或类似的概念。 使用契约devise(Design by Contract) ,相关数量的函数将具有后置条件 ,因此在“Empty()”之后对象将是“IsEmpty()”,并且在“Encode()”之后,消息string将是“IsEncoded不需要检查。 以同样的方式,并且有点对称,在每次调用过程“X()”之前,都不要调用“IsXFeasible()”特殊函数。 因为你通常通过devise知道你正在履行X的先决条件。

您可以创build另一个macros,以避免使用临时variables:

 #ifndef NDEBUG #define Verify(x) assert(x) #else #define Verify(x) ((void)(x)) #endif // asserts that Func()==1 in debug mode, or calls Func() and ignores return // value in release mode (any braindead compiler can optimize away the comparison // whose result isn't used, and the cast to void suppresses the warning) Verify(Func() == 1); 

这是一个糟糕的使用断言,恕我直言。 断言不是一个错误报告工具,它是为了声明前提条件。 如果结果没有用在其他地方,这不是一个先决条件。

你可以使用:

 Check( Func() == 1 ); 

并根据需要实现您的Check(bool)函数。 它可以使用断言或抛出特定的exception,写入日志文件或控制台,在debugging和发布中有不同的实现,或所有的组合。

最简单的事情是只声明/分配这些variables,如果断言存在。 NDEBUGmacros是专门定义的,如果断言不会被影响(完成这种方式只是因为-DNDEBUG是一个方便的方法来禁用debugging,我认为),所以这个@ Jardel的答案的副本应该工作(比较评论@ AdamPeterson就这个答案):

 #ifndef NDEBUG int Result = #endif Func(); assert(Result == 1); 

或者,如果这不适合你的口味,各种各样的变种是可能的,例如:

 #ifndef NDEBUG int Result = Func(); assert(Result == 1); #else Func(); #endif 

总的来说,要注意,不同的NDEBUGmacros状态不同的翻译单元是不可能的,尤其是re。 断言或公共头文件中的其他条件内容。 危险的是,你或者你的库的用户可能会意外地实例化一个内联函数的不同定义,这个定义是在库的已编译部分中使用的,并且默认违反了一个定义规则,并且使得运行时行为不确定。

您应该在返回值之前移动函数内部的断言。 你知道返回值不是一个未引用的局部variables。

而且,无论如何,这个function是更有意义的,因为它创build了一个自我拥有的单元,拥有自己的前后条件。

机会是,如果函数返回一个值,你应该在释放模式下进行一些错误检查,无论如何这个返回值。 所以它不应该是一个未引用的variables。

编辑,但在这种情况下,发布条件应该是X(见注释):

我坚决不同意这一点,应该能够从input参数中确定发布条件,以及是否是成员函数,任何对象状态。 如果一个全局variables修改了函数的输出,那么该函数应该重新构造。

当然你使用macros来控制你的断言定义,比如“_ASSERT”。 所以,你可以这样做:

 #ifdef _ASSERT int Result = #endif /*_ASSERT */ Func(); assert(Result == 1); 
 int Result = Func(); assert( Result == 1 ); Result; 

这将使编译器停止抱怨Result未被使用。

但是你应该考虑使用一个在运行时有用的assert版本,比如将描述性错误logging到可以从生产环境中检索的文件。

我会使用以下内容:

 #ifdef _DEBUG #define ASSERT(FUNC, CHECK) assert(FUNC == CHECK) #else #define ASSERT(FUNC, CHECK) #endif ... ASSERT(Func(), 1); 

这样,对于发布版本,编译器甚至不需要为断言产生任何代码。

如果这个代码是在一个函数中,那么就执行并返回结果:

 bool bigPicture() { //Check the results bool success = 1 != Func(); assert(success == NO, "Bad times"); //Success is given, so... actOnIt(); //and return success; } 

大多数答案build议在Release版本中使用static_cast<void>(expression)技巧来抑制警告,但如果您的目的是使检查真正为Debug ,则这实际上并不理想。 这个断言macros的目标是:

  1. Debug模式下执行检查
  2. Release模式下什么也不做
  3. 在所有情况下不发出警告

问题在于,虚空的方法未能达到第二个目标。 虽然没有任何警告,但您传递给断言macros的expression式仍将被评估 。 例如,如果你做一个variables检查​​,那可能不是什么大问题。 但是如果在断言检查中调用一些函数,如ASSERT(fetchSomeData() == data); (这是我的经验非常普遍)? fetchSomeData()函数仍将被调用。 它可能是快速和简单的,或者可能不是。

你真正需要的不仅仅是警告抑制,而且可能更重要的是不对只debugging检查expression式进行评估 。 这可以通过一个简单的技巧来实现,我从一个专门的Assert库中获取:

 void myAssertion(bool checkSuccessful) { if (!checkSuccessful) { // debug break, log or what not } } #define DONT_EVALUATE(expression) \ { \ true ? static_cast<void>(0) : static_cast<void>((expression)); \ } #ifdef DEBUG # define ASSERT(expression) myAssertion((expression)) #else # define ASSERT(expression) DONT_EVALUATE((expression)) #endif // DEBUG int main() { int a = 0; ASSERT(a == 1); ASSERT(performAHeavyVerification()); return 0; } 

所有的魔法都在DONT_EVALUATEmacros中。 很明显,至less在逻辑上来说 ,expression式的评估在其内部是不需要的。 为了加强这一点,C ++标准保证只评估一个条件操作符的分支。 这是报价:

5.16条件运算符[expr.cond]

逻辑或expression式? expression式:赋值expression式

条件expression式从右到左分组。 第一个expression式被上下文转换为bool。 它被评估,如果它是真的,条件expression式的结果是第二个expression式的值,否则第三个expression式的值。 只有其中一个expression式被评估。

我已经在GCC 4.9.0,铿锵3.8.0,VS2013更新4,VS2015更新4中testing了这种方法,其中最严厉的警告级别。 在所有情况下都没有任何警告,检查expression式从来没有在Release版本中进行过评估(实际上整个事情都被完全优化了)。 不过要记住,如果你把expression式放在断言macros里面,你会很快陷入麻烦,尽pipe这首先是非常糟糕的做法。

另外,我也希望静态分析器可以用这种方法警告“expression式的结果总是不变的”(或类似的东西)。 我已经用叮当声,VS2013,VS2015静态分析工具进行了testing,并没有得到这种警告。