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

Hex*_*gon 55 c++ warnings assertions

有时局部变量的唯一目的是在assert()中检查它,就像这样 -

int Result = Func();
assert( Result == 1 );
Run Code Online (Sandbox Code Playgroud)

在Release版本中编译代码时,通常会禁用assert(),因此此代码可能会生成有关Result已设置但从未读取的警告.

可能的解决方法是 -

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}
Run Code Online (Sandbox Code Playgroud)

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

我正在寻找一种替代方法来表达这个assert()的方式不会导致警告,但仍然易于使用并避免更改assert()的语义.

(在此区域代码中使用#pragma禁用警告不是一种选择,并且降低警告级别以使其消失也不是一种选择......).

Gra*_*row 51

我们使用宏来明确指出何时未使用的东西:

#define _unused(x) ((void)(x))
Run Code Online (Sandbox Code Playgroud)

然后在你的例子中,你有:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy
Run Code Online (Sandbox Code Playgroud)

这样(a)生产构建成功,并且(b)在代码中显而易见的是变量未被设计使用,而不是仅仅被遗忘.当不使用函数的参数时,这尤其有用.

  • 此外,仍会调用 Func(除非优化器知道它没有副作用) (4认同)
  • @xtofl:这是一个观点,但几乎可以肯定你*需要调用*Func() - 否则你可能只是写了"assert(Func()== 1);" 首先. (4认同)
  • 直到后来使用结果,然后它只是令人困惑:)但是很好的建议+1。 (3认同)
  • 您还可以将您的`assert`设为一个宏,例如:#define assert(X,Y)__assert(X,Y); _unused(X)`其中X是您的变量,Y是条件。在您的情况下的用法:`assert(Result,1);` (2认同)

小智 27

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

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

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
Run Code Online (Sandbox Code Playgroud)

  • 对于懒惰,在发布版本中:#define ASSERT(X)do {(void)sizeof(x); } while(0) (3认同)

Ada*_*eld 9

您可以创建另一个允许您避免使用临时变量的宏:

#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);
Run Code Online (Sandbox Code Playgroud)


Dan*_*nas 8

int Result = Func();
assert( Result == 1 );
Run Code Online (Sandbox Code Playgroud)

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

Func();
Run Code Online (Sandbox Code Playgroud)

但是Func非void,即它返回一个结果,即它是一个查询.

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

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

Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());
Run Code Online (Sandbox Code Playgroud)

比.更好

Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);
Run Code Online (Sandbox Code Playgroud)

前者没有给你任何警告,后者确实如此.

所以,简而言之,我的回答是:不要写这样的代码:)

更新(1):您询问了有关命令查询分离原则的参考.维基百科信息量很大.我在Bertrand Meyer的第二版"面向对象软件构建"中读到了这种设计技术.

更新(2): j_random_hacker注释"OTOH,每个"命令"先前返回值的函数f()现在必须设置一些变量last_call_to_f_succeeded或类似".这仅适用于在合同中不承诺任何内容的函数,即可能"成功"或未成功的函数,或类似的概念.使用契约设计,相关数量的函数将具有后置条件,因此在"Empty()"之后,对象将是"IsEmpty()",并且在"Encode()"之后,消息字符串将是"IsEncoded()",无需检查.以同样的方式,并且有些对称,在每次调用过程"X()"之前,不要调用特殊函数"IsXFeasible()"; 因为你通常会在设计中知道你在通话时已经达到了X的先决条件.

  • 这个建议太笼统了.怎么样`list.Remove`?在这里你可能*需要*来获取元素是否被删除的返回值.`list.WasSuccessfullyRemoved()`会是假的. (16认同)
  • 从函数返回错误代码是完全正常的,但你似乎该死的做法. (3认同)
  • @Daniel:感谢您的更新.我可以看到这方面的优势.OTOH,先前返回的值的每一个"命令"函数f()现在必须为重入/多线程代码一些变量last_call_to_f_succeeded或类似,可能导致问题(如在文章中指出的).但是,是的,CQS看起来似乎是一种有效的,逻辑简化的方法,值得在可能的情况下应用. (2认同)

Ael*_*ian 6

使用 C++17 我们可以做到:

[[maybe_unused]] int Result = Func();
Run Code Online (Sandbox Code Playgroud)

尽管与断言替换相比,它涉及一些额外的输入。看到这个答案

注意:添加此内容是因为这是谷歌第一次点击“c++断言未使用的变量”。


Gat*_*har 5

对于最新的C ++,我只会说:

[[maybe_unused]] int Result = Func();
assert( Result == 1 );
Run Code Online (Sandbox Code Playgroud)

有关此属性的更多详细信息,请参见https://en.cppreference.com/w/cpp/language/attributes/maybe_unused

(void)Result技巧相比,我更喜欢它,因为您可以直接修饰变量声明,而不是事后才添加。

  • 值得一提的是“最近的C++”==自C++17以来 (2认同)