C++ 在发布版本中使用assert()时避免未使用的变量警告

C++ 在发布版本中使用assert()时避免未使用的变量警告,c++,warnings,assertions,C++,Warnings,Assertions,有时,局部变量仅用于在assert()中检查它,如下所示- int Result = Func(); assert( Result == 1 ); 在发布版本中编译代码时,assert()通常是禁用的,因此此代码可能会生成一条关于结果已设置但从未读取的警告 一个可能的解决办法是- int Result = Func(); if ( Result == 1 ) { assert( 0 ); } 但是它需要太多的输入,看起来不容易,并且导致总是检查条件(是的,编译器可能会优化检查,但仍然

有时,局部变量仅用于在assert()中检查它,如下所示-

int Result = Func();
assert( Result == 1 );
在发布版本中编译代码时,assert()通常是禁用的,因此此代码可能会生成一条关于结果已设置但从未读取的警告

一个可能的解决办法是-

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}
但是它需要太多的输入,看起来不容易,并且导致总是检查条件(是的,编译器可能会优化检查,但仍然如此)

我正在寻找另一种方式来表达这个assert(),这种方式不会引起警告,但仍然简单易用,并且避免更改assert()的语义


(在此代码区域使用#pragma禁用警告不是一个选项,降低警告级别使其消失也不是一个选项…。

这是assert、IMHO的一个错误用法。Assert并不是一个错误报告工具,它是用来断言前提条件的。如果结果未在其他地方使用,则它不是先决条件。

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

另外,不管怎么说,在函数内部更有意义,因为它创建了一个具有自己的前置和后置条件的自包含单元

如果函数返回一个值,那么无论如何,您都应该在释放模式下对此返回值进行某种错误检查。因此,它不应该是一个未引用的变量

编辑,但在这种情况下,post条件应为X(请参见注释):


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

我们使用宏来明确指示什么时候未使用:

#define _unused(x) ((void)(x))
在您的示例中,您将有:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy
这样一来,(a)生产构建成功,(b)代码中很明显,设计没有使用该变量,而不是忘记它。这在不使用函数的参数时特别有用

int Result = Func();
assert( Result == 1 );
Result;
这将使编译器停止抱怨没有使用结果

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

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的文章中读到了这种设计技巧

更新(2):j_random_hacker comments“oth,以前返回值的每个“command”函数f()现在必须设置一些变量last_call_to_f_successed或类似”。这仅适用于合同中没有承诺任何内容的函数,即可能“成功”或“失败”的函数或类似概念。使用合约设计,相关数量的函数将具有后条件,因此在“Empty()”之后,对象将是“IsEmpty()”,在“Encode()”之后,消息字符串将是“IsEncoded()”,无需检查。以同样的方式,并且在某种程度上是对称的,在每次调用过程“X()”之前都不调用特殊函数“IsXFeasible()”;因为您通常通过设计知道您在通话时满足了X的先决条件。

您可以使用:

Check( Func() == 1 );
并根据需要实现检查(bool)功能。它可以使用assert,也可以抛出特定异常,写入日志文件或控制台,在调试和发布中有不同的实现,或者是所有实现的组合。

我将使用以下方法:

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

...

ASSERT(Func(), 1);

这样,对于release build,编译器甚至不需要为assert生成任何代码。

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

#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);

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

#ifdef NDEBUG
#定义ASSERT(x)do{(void)sizeof(x);}while(0)
#否则
#包括
#定义断言(x)断言(x)
#恩迪夫

当然,您可以使用宏来控制断言定义,例如“\u assert”。因此,您可以这样做:

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

如果此代码在函数中,则执行操作并返回结果:

bool bigPicture() {

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

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

   //and
   return success;
}

大多数答案建议在
Release
构建中使用
static\u cast(expression)
技巧来抑制警告,但如果您的目的是只进行真正的
Debug
检查,那么这实际上是次优的。有关断言宏的目标是:

  • Debug
    模式下执行检查
  • Release
    模式下不执行任何操作
  • 在所有情况下都不发出警告
  • 问题在于,空投法无法达到第二个目标。虽然没有警告,但仍将对传递给断言宏的表达式求值。例如,如果你只是做一个变量检查,那可能不是
    bool bigPicture() {
    
       //Check the results
       bool success = 1 != Func();
       assert(success == NO, "Bad times");
    
       //Success is given, so...
       actOnIt();
    
       //and
       return success;
    }
    
    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;
    }
    
    #ifndef NDEBUG
    int Result =
    #endif
    Func();
    assert(Result == 1);
    
    #ifndef NDEBUG
    int Result = Func();
    assert(Result == 1);
    #else
    Func();
    #endif
    
    // 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);
    }
    
    [[maybe_unused]] int Result = Func();
    
    [[maybe_unused]] int Result = Func();
    assert( Result == 1 );