69

有时,局部变量仅用于在 assert() 中检查它,就像这样 -

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

在 Release 构建中编译代码时,assert()s 通常被禁用,因此此代码可能会产生关于 Result 已设置但从未读取的警告。

一个可能的解决方法是 -

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

但是它需要太多的输入,看起来并不容易,并且会导致总是检查条件(是的,编译器可能会优化检查,但仍然如此)。

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

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

4

16 回答 16

58

我们使用宏来明确指示何时未使用某些内容:

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

然后在您的示例中,您将拥有:

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

这样(a)生产构建成功,并且(b)在代码中很明显该变量是设计未使用的,而不是只是被遗忘了。这在不使用函数的参数时特别有用。

于 2009-04-22T14:02:20.923 回答
33

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

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

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
于 2009-06-12T09:25:23.900 回答
26

从 C++17 开始,变量可以用属性修饰。

[[maybe_unused]] int Result = Func();
assert( Result == 1 );

有关详细信息,请参阅https://en.cppreference.com/w/cpp/language/attributes/maybe_unused

这比(void)Result技巧要好,因为您直接装饰变量声明,而不是事后添加一些东西。

于 2018-12-20T08:39:37.387 回答
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);
于 2009-04-22T15:53:18.770 回答
8
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):您要求提供有关Command-Query Separation Principle的参考资料。维基百科信息量很大。我在Bertrand Meyer的《面向对象的软件构建》第二版中读到了这种设计技术。

更新 (2): j_random_hacker 评论“OTOH,以前返回值的每个“命令”函数 f() 现在必须设置一些变量 last_call_to_f_succeeded 或类似变量”。这仅适用于在其合约中不承诺任何内容的函数,即可能“成功”或不“成功”的函数,或类似概念。使用契约式设计,相关数量的函数将具有后置条件,因此在“Empty()”之后,对象将为“IsEmpty()”,在“Encode()”之后,消息字符串将为“IsEncoded()”,其中无需检查。以同样的方式,并且有点对称,您不会在每次调用过程“X()”之前调用特殊函数“IsXFeasible()”;

于 2009-04-22T14:25:59.870 回答
4

你可以使用:

Check( Func() == 1 );

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

于 2009-04-22T14:30:40.530 回答
3

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

[[maybe_unused]] int Result = Func();

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

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

于 2018-10-10T16:07:26.430 回答
2

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

另外,无论如何,在函数内部更有意义,因为它创建了一个自包含单元,该单元具有其 OWN 前置和后置条件。

很有可能,如果函数返回一个值,那么无论如何您都应该在发布模式下对该返回值进行某种错误检查。所以它不应该是一个未引用的变量。

编辑,但在这种情况下,后置条件应该是 X(见评论):

我强烈不同意这一点,应该能够从输入参数中确定后置条件,如果它是成员函数,则可以确定任何对象状态。如果一个全局变量修改了函数的输出,那么函数应该被重构。

于 2009-04-22T13:56:59.267 回答
2

大多数答案建议static_cast<void>(expression)在构建中使用技巧Release来抑制警告,但如果您的意图是真正进行检查,这实际上是次优的Debug。相关断言宏的目标是:

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

问题是void-cast方法无法达到第二个目标。虽然没有警告,但您传递给断言宏的表达式仍将被评估。例如,如果您只是进行变量检查,那可能没什么大不了的。但是如果你在你的断言检查中调用一些函数ASSERT(fetchSomeData() == data);(这在我的经验中很常见)怎么办?该fetchSomeData()函数仍将被调用。它可能快速而简单,也可能不是。

您真正需要的不仅是警告抑制,而且可能更重要的是 -不评估仅调试检查表达式。这可以通过我从专门的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_EVALUATE宏中。很明显,至少从逻辑上讲,在它内部永远不需要对表达式的求值。为了加强这一点,C++ 标准保证只评估条件运算符的一个分支。这是报价:

5.16 条件运算符 [expr.cond]

逻辑或表达式?表达式:赋值表达式

条件表达式从右到左分组。第一个表达式根据上下文转换为布尔值。它被评估,如果为真,则条件表达式的结果是第二个表达式的值,否则是第三个表达式的值。仅评估这些表达式之一。

我已经在 GCC 4.9.0、clang 3.8.0、VS2013 Update 4、VS2015 Update 4 中测试了这种方法,警告级别最苛刻。在所有情况下都没有警告,并且检查表达式永远不会在Release构建中评估(实际上整个事情已经完全优化了)。请记住,使用这种方法,如果将具有副作用的表达式放在断言宏中,您将很快遇到麻烦,尽管这首先是一种非常糟糕的做法。

此外,我希望静态分析器可能会使用这种方法警告“表达式的结果始终是恒定的”(或类似的东西)。我已经使用 clang、VS2013、VS2015 静态分析工具对此进行了测试,并且没有收到此类警告。

于 2016-04-23T11:44:51.993 回答
2

最简单的事情是仅在断言存在时才声明/分配这些变量。如果断言不会受到影响,则该NDEBUG宏是专门定义的(我认为这样做是因为这是一种禁用调试的便捷方法),因此@Jardel 答案的调整副本应该可以工作(参见@AdamPeterson 的评论)回答):-DNDEBUG

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

或者,如果这不符合您的口味,则可以使用各种变体,例如:

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

一般来说,对于这些东西,要小心,不同的翻译单元不可能用不同的NDEBUG宏状态来构建——尤其是 re. 公共头文件中的断言或其他条件内容。危险在于,您或您的库的用户可能会意外地实例化与在库的已编译部分中使用的定义不同的内联函数定义,从而悄悄地违反一个定义规则并使运行时行为未定义。

于 2017-02-04T13:34:35.383 回答
1

这是对断言的错误使用,恕我直言。Assert 并不意味着作为错误报告工具,它意味着断言先决条件。如果 Result 没有在其他地方使用,则它不是前提条件。

于 2009-04-22T13:50:43.870 回答
1

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

#ifdef _ASSERT 
int Result =
#endif /*_ASSERT */
Func();
assert(Result == 1);
于 2012-08-27T22:55:13.583 回答
0
int Result = Func();
assert( Result == 1 );
Result;

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

但是您应该考虑使用在运行时做一些有用的事情的 assert 版本,例如将描述性错误记录到可以从生产环境中检索的文件中。

于 2009-04-22T14:12:42.943 回答
0

我会使用以下内容:

#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif

...

ASSERT(Func(), 1);

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

于 2009-04-22T15:42:54.210 回答
0

如果此代码在函数内部,则执行并返回结果:

bool bigPicture() {

   //Check the results
   bool success = 1 != Func();
   assert(success == NO, "Bad times");

   //Success is given, so...
   actOnIt();

   //and
   return success;
}
于 2014-10-05T19:41:19.847 回答
0
// Value is always computed.  We also call assert(value) if assertions are
// enabled.  Value is discarded either way.  You do not get a warning either
// way.  This is useful when (a) a function has a side effect (b) the function
// returns true on success, and (c) failure seems unlikely, but we still want
// to check sometimes.
template < class T >
void assertTrue(T const &value)
{
  assert(value);
}

template < class T >
void assertFalse(T const &value)
{ 
  assert(!value);
}
于 2018-02-19T03:36:38.113 回答