如何在自定义断言中最好地避免未使用变量警告而不使用sizeof?

4

根据http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/中的建议,我已经使用自己的emp_assert版本一段时间了。当设置了NDEBUG时,我的assert如下:

#define emp_assert(EXPR) ((void) sizeof(EXPR) )

该定义确保在编译器中,EXPR 中的任何变量仍然被视为“使用过”,但不会影响运行时性能。
不幸的是,我最近发现,在 assert 中使用 lambda 会产生编译错误,因为 lambda 无法放入 sizeof 中。
我的选择似乎有:
1. 简单地去除 sizeof;我的代码中很少有未使用变量的情况,可以手动处理它们。
2. 避免在 assert 中使用 labda。
3. 想出一个可以产生同样效果的 sizeof 替代方案。
目前,选项1是我的首选,但我使用这个断言系统来完成许多项目,并且可能在一段时间内遇到问题。
选项2似乎太过局限,特别是因为我可以想象未来会出现意外的交互。
选项3将是最优雅的解决方案,但我需要帮助想出如何实现它的想法。
编辑:以下是一些示例代码,以说明问题。
#include <iostream>
#include <algorithm>
#include <vector>

#define NDEBUG

// Relevant excerpt from "emp_assert.h"
#ifdef NDEBUG
#define emp_assert(EXPR) ((void) sizeof(EXPR))
#else
#define emp_assert(EXPR)                           \
  do { if ( !(EXPR) ) {                            \
      std::cerr << "Assert Error (In " << __FILE__ \
                << " line " << __LINE__            \
                << "): " << #EXPR << std::endl;    \
      abort(); }                                   \
  } while (0)
#endif

// Code to trigger the problem (asserting that all ints in a vector are less than 8.)
int main()
{
  std::vector<int> v = {1, 2, 3, 4, 8};
  emp_assert( std::all_of(v.begin(), v.end(), [](int i){return i < 8;}) );
}

#define NDEBUG注释掉,以查看代码在编译时是否正确并在运行时触发断言。(如果不想触发断言,请从向量中删除8)。

我使用g++ -std=c++11 file.cc进行编译。


1
emp_assert 的作用是什么?它与 assertstatic_assert 有何不同之处? - M.M
1
你能否提供一些使用 emp_assert 并展示问题的代码吗? - M.M
完成!新的示例应该会像之前一样报错。这个错误只会在使用lambda表达式的断言中出现;如果你改变emp_assert,那么它就能正常工作了。 - Charles Ofria
1
向委员会报告一个错误,并请求他们移除对未求值操作数中lambda表达式的限制? - T.C.
1
@T.C. 看起来你是对的;这是一个实际的限制,因此不可能找到一种替代方案,而不评估lambda。另一方面,在这里有一个很好的讨论,可能通过使用常量表达式提供一些可能性:https://dev59.com/F2Eh5IYBdhLWcg3wnEcc - Charles Ofria
显示剩余7条评论
2个回答

3

我相信我找到了一个解决方案。由于lambda表达式不允许在未求值的操作数中使用,但在常量表达式的未求值部分中是允许的,因此我们应该能够利用这一事实。

具体来说,当NDEBUG处于ON状态时,我将我的#define设置为:

#define emp_assert(EXPR) {                             \
   constexpr bool __emp_assert_tmp = false && (EXPR);  \
   (void) __emp_assert_tmp;                            \
}
constexpr 确保剩余的表达式在编译时计算。然而,false && (EXPR) 短路使得 EXPR 不会被计算,但其中的变量不被视为未使用。
变量 __emp_assert_tmp 在其自己的作用域内创建,因此多个assert不会冲突。同时,高度具体的名称将确保我们不会意外遮蔽EXPR中使用的变量。任何合理的优化编译器都应该完全删除额外的变量,因此我认为这不应该引起优化问题。
在我的测试中,EXPR 中的任何内容都没有执行,lambda 也不会影响它,一切似乎都正常工作。
如果您看到我忽略的任何问题,请告诉我。

1
这将使用 EXPR 的子表达式,而不像 sizeof - Ben Voigt
为什么 #define emp_assert(EXPR) { (void)EXPR;} 不够用? - BЈовић

0
为什么要使用sizeof?我看不出在你的例子中使用sizeof有任何意义,为什么不直接这样定义:
#define emp_assert(exp) ((void) exp)

如果你想要避免未使用变量的警告,你可以使用 C++17 属性 [maybe_unused] 来抑制未使用实体的警告。例如:

[[maybe_unused]]
auto l = [](int i){return i < 8;};

此外,C++20 中提供了未求值上下文中的 lambda 表达式(但目前只有 gcc-9 实现),因此以下代码可以在 -std=c++2a 模式下使用 gcc-9 编译,并且您的问题将自动解决。
static_assert(sizeof([](int i){return i < 8;}) == 1);

请查看wandbox test以获取您的示例。


1
sizeof()可以防止任何意外的副作用。如果assert包含一个函数调用,该调用会更改任何变量,则可能会影响结果,但如果它在sizeof()中,那么这将确保该调用实际上不会发生。 - Charles Ofria
虽然如此说,你是对的,现在C++17解决了一个问题,而C++20将解决另一个问题。 - Charles Ofria

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接