在发布版本中使用声明()时避免未使用的变量警告

有时一个局部变量仅仅用于在 asser()中检查它,比如-

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

在发布版本中编译代码时,断言()通常是禁用的,因此这段代码可能会产生关于正在设置但从未读取的 Result 的警告。

一个可能的解决办法是-

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

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

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

(在此代码区域使用 # 杂注禁用警告不是一个选项,降低警告级别以消除警告也不是一个选项... ...)。

23447 次浏览

恕我直言,这是对断言的不恰当使用。断言并不意味着错误报告工具,它意味着断言前提条件。如果 Result 不在其他地方使用,那么它就不是一个先决条件。

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

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

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

编辑,但在这种情况下,后置条件应该是 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;

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

但是您应该考虑使用在运行时执行一些有用功能的断言版本,比如对可以从生产环境中检索到的文件执行日志描述性错误。

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) : 您要求提供关于 命令-查询分离原则的参考资料。维基百科的信息量相当大。我在伯特兰 · 迈耶的 面向对象软件构建,第2版中读到过这种设计技巧。

更新(2) : j _ Random _ hacker 注释“ OTOH,以前返回值的每个“命令”函数 f ()现在都必须设置某个变量 last _ call _ to _ f _ Success 或类似的值”。这只适用于在契约中不承诺任何东西的函数,即可能“成功”或不成功的函数,或类似的概念。对于 合同设计,相应数量的函数将具有 后置条件,因此在“ Empty ()”之后,对象将是“ IsEmpty ()”,在“ Encode ()”之后,消息字符串将是“ IsEncode ()”,无需检查。同样,在每次调用过程“ X ()”之前,你不会调用一个特殊的函数“ IsX 可行()”; 因为你通常知道你在调用的时候满足了 X 的先决条件。

你可以用:

Check( Func() == 1 );

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

我会用以下方法:

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


...


ASSERT(Func(), 1);

这样,对于版本构建,编译器甚至不需要生成任何用于断言的代码。

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

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

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

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

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

当然,您可以使用宏来控制断言定义,比如“ _ 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_cast<void>(expression)技巧来抑制警告,但是如果您的意图是进行真正的只有 Debug的检查,那么这实际上是次优的。所涉及的断言宏观的目标是:

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

问题在于,无效铸造方法无法实现第二个目标。虽然没有警告,但是传递给断言宏的表达式仍然是 评估。例如,如果您只是做一个变量检查,这可能不是一个大问题。但是,如果在断言检查中调用某个函数,比如 ASSERT(fetchSomeData() == data);(这在我的经验中非常常见) ,该怎么办呢?仍将调用 fetchSomeData()函数。它可能是快速和简单的,也可能不是。

您真正需要的不仅是警告抑制,而且可能更重要的是仅调试检查表达式的 不评估。这可以通过我从专门的 坚持库中学到的一个简单技巧来实现:

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 ]

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

条件表达式从右向左分组 上下文转换为 bool。它被计算,如果为真,则 条件表达式的结果是第二个 表达式,否则就是第三个表达式 表达式计算。

我已经在海湾合作委员会4.9.0,叮当3.8.0,VS2013更新4,VS2015更新4与最严厉的警告水平测试这种方法。在所有的情况下,没有警告,检查表达式从来没有在 Release构建中计算(事实上,整个事情完全优化了)。不要忘记,如果在断言宏中放入具有副作用的表达式,那么使用这种方法会很快陷入麻烦,尽管这首先是一种非常糟糕的做法。

另外,我希望静态分析器可以警告这种方法“表达式的结果总是常量”(或类似的东西)。我已经用 clang,VS2013,VS2015静态分析工具对此进行了测试,没有得到任何类似的警告。

最简单的方法是,如果断言存在,那么只声明/赋值这些变量。如果断言 不会受到影响,NDEBUG宏就会被特别定义(我认为这样做只是因为 -DNDEBUG是一种禁用调试的方便方法) ,所以@Jardel 的答案的这个修改版本应该可以工作(参见@AdamPeterson 对这个答案的评论) :

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

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

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

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

// 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);
}

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

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

尽管与断言替换相比,它涉及一些额外的类型。

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

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

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

详情请参阅 https://en.cppreference.com/w/cpp/language/attributes/maybe_unused

这比 (void)Result技巧要好,因为您可以直接修饰变量声明,而不是事后添加一些内容。

我没有成功地使用[[可能 _ 未使用]] ,但是 您可以使用未使用的属性

int Result __attribute__((__unused__)) = Func();

变量-属性