用大的评论回复评论是个坏主意,所以这是我对以下主题的详细回复:
虽然这是可能的,但在所有测试用例文件中包含该定义会很痛苦。此外,这不仅限于 BOOST_REQUIRE,还适用于 assert、SDL_Assert 或用户可能使用的任何其他自定义宏。
应该明白,有三种类型的测试宏,每种都应该单独讨论。
第一种类型的宏只是警告您在调试版本中出现了问题。一个典型的例子是assert
宏。以下代码将导致 PVS-Studio 分析器生成警告:
T* p = dynamic_cast<T *>(x);
assert(p);
p->foo();
分析器将在此处指出可能的空指针取消引用,并且是正确的。使用的检查assert
是不够的,因为它将从发布版本中删除。也就是说,事实证明没有检查。实现它的更好方法是将代码重写为如下所示:
T* p = dynamic_cast<T *>(x);
if (p == nullptr)
{
assert(false);
throw Error;
}
p->foo();
此代码不会触发警告。
你可能会争辩说你 100% 确定dynamic_cast
永远不会回来nullptr
。我不接受这个论点。如果你完全确定演员表总是正确的,你应该使用更快的static_cast
. 如果您不确定,则必须在取消引用之前测试指针。
好吧,好吧,我明白你的意思了。您确定代码没问题,但您需要使用 dynamic_cast 检查以防万一。好的,然后使用以下代码:
assert(dynamic_cast<T *>(x) != nullptr);
T* p = static_cast<T *>(x);
p->foo();
我不喜欢它,但至少它更快,因为较慢的 dynamic_cast 运算符将在 Release 版本中被忽略,而分析器将保持沉默。
继续下一种类型的宏。
第二种类型的宏只是警告您在 Debug 版本中出现问题并在测试中使用。它们与前一种类型的不同之处在于,如果条件为假,它们会停止被测算法并生成错误消息。
这些宏的基本问题是函数没有被标记为不返回。这是一个例子。
假设我们有一个通过抛出异常来生成错误消息的函数。这是它的声明的样子:
void Error(const char *message);
这就是测试宏的声明方式:
#define ENSURE(x) do { if (!x) Error("zzzz"); } while (0)
使用指针:
T* p = dynamic_cast<T *>(x);
ENSURE(p);
p->foo();
分析器将发出有关可能的空指针取消引用的警告,但代码实际上是安全的。如果指针为空,Error
函数将抛出异常,从而阻止指针解引用。
我们只需要使用其中一种函数注释方式告诉分析器,例如:
[[noreturn]] void Error(const char *message);
或者:
__declspec(noreturn) void Error(const char *message);
这将有助于消除错误警告。因此,如您所见,在大多数情况下使用自己的宏很容易修复问题。
但是,如果您处理来自第三方库的粗心实现的宏,则可能会更棘手。
这将我们引向第三种类型的宏。您无法更改它们,分析器也无法弄清楚它们的工作原理。这是一种常见的情况,因为宏可能以非常奇特的方式实现。
在这种情况下,您可以选择三个选项:
- 使用文档中描述的一种误报抑制手段来抑制警告;
- 使用我在上一个答案中描述的技术;
- 给我们发电子邮件。
我们正在逐渐增加对流行库中各种棘手宏的支持。事实上,分析器已经熟悉了你可能遇到的大多数特定宏,但程序员的想象力是无穷无尽的,我们只是无法预见每一种可能的实现。