自定义"assert"宏,支持逗号和错误消息

Custom `assert` macro that supports commas and error message

本文关键字:quot 消息 错误 assert 自定义 支持      更新时间:2023-10-16

我想创建<cassert>中定义的assert宏的自定义版本,该版本在断言失败时显示错误消息。


所需用途:

custom_assert(AClass<T1, T2>::aBoolMethod(), "aBoolMethod must be true");


缺陷测试实现:

#define custom_assert(mCondition, mMessage) ...
// This fails because mCondition may have commas in it
#define custom_assert(..., mMessage)
// Not sure about this either - mMessage may be an expression containing commas
// as well

如何正确实现将布尔表达式(可能带有逗号)作为第一个参数,将字符串表达式(可能带逗号)作为第二个参数的自定义断言?

或者有没有一种方法可以在不使用宏的情况下实现断言?

您非常接近,您需要使用的只是:

#define myAssert(message, ...) do { 
if(!(__VA_ARGS__)) { 
/*error code*/ 
} 
} while(0)

特殊的预处理器变量__VA_ARGS__将扩展到在三个点的位置传递的任何内容,包括所有coma。

请注意,预处理器根本不会解释条件中的逗号,它只会将它们原样粘贴到if()语句中。如果您想要传递模板化条件,正如注释所暗示的那样,这正是您想要的。

消息字符串中的逗号也不是问题,因为预处理器了解字符串文字,并且不解释双引号中的任何内容。

直接的

assert(AClass<T1, T2>::aBoolMethod() && "aBoolMethod must be true");

失败:

错误:宏"assert"传递了2个参数,但只接受了1个

但如果在第一个参数周围添加一对额外的括号,它就可以工作了。像这样:

#include <cassert>
template <typename A, typename B>
struct C { 
bool f() { return false; }
};
int main() {
assert((C<int,int>().f()) && "some message, with comma");
//     ^                ^
}

注:亚当·罗森菲尔德在评论中也指出了这一点。

__VA_ARGS__方法相比,可能唯一的好处是它不会向用户转储另一个宏。如果你忘记了括号,你可能会得到一个编译时错误,所以我认为这是一个安全的解决方案。

为了完整起见,我发布了一个在C++中的2个文件中的插件断言宏实现:

#include <pempek_assert.h>
int main()
{
float min = 0.0f;
float max = 1.0f;
float v = 2.0f;
PEMPEK_ASSERT(v > min && v < max,
"invalid value: %f, must be between %f and %f", v, min, max);
return 0;
}

将提示您:

Assertion 'v > min && v < max' failed (DEBUG)
in file e.cpp, line 8
function: int main()
with message: invalid value: 2.000000, must be between 0.000000 and 1.000000
Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:

何处

  • (I)gnore:忽略当前断言
  • Ignore(F)orever:记住触发断言的文件和行在程序的剩余执行中忽略它
  • Ignore(A)ll:忽略所有剩余断言(所有文件和行)
  • (D) ebug:如果附加,则闯入调试器,否则为abort()(在Windows上,系统将提示用户附加调试器)
  • A(b)端口:立即呼叫abort()

你可以在那里找到更多关于它的信息:

  • 博客文章
  • GitHub项目

希望能有所帮助。

我不完全确定你所说的"带逗号的布尔表达式"是什么意思。如果你碰巧在条件中使用默认逗号运算符,那么简单地用逗号包装宏展开(如下面的示例所示)可以防止解析错误,但默认逗号运算符的作用与&&不同。如果你的意思是你想要这样的东西:

my_assert(condition1, condition2, message1, message2);

你运气不好。任何API都应该如何判断条件停止和消息开始的位置。只需使用&&即可。您可以使用以下相同的技巧来处理消息部分,还可以创建一个my_condition_set宏,该宏允许您编写以下内容:

my_asssert(my_condition_set(condition1, condition2), message1, message2);

进入消息部分,这是标准assert宏往往缺乏的棘手部分和最有用的部分,技巧将归结为要么使用覆盖operator,(逗号运算符)的自定义消息输出类型,要么使用可变模板。

该宏使用可变的宏支持和一些技巧来处理逗号。请注意,我在这里发布的代码没有一个是直接测试的;这一切都来自我过去写的自定义断言宏的内存。

使用逗号运算符重载的版本,适用于C++98编译器:

struct logger {
template <typename T>
logger& operator,(const T& value) {
std::cerr << value;
return *this;
}
};
#define my_assert(condition, ...) do{ 
if (!(condition)) { 
(logger() , __VA_ARGS__); 
std::terminate(); 
} 
}while(false)

logger()表达式创建logger类型的新实例。然后,__VA_ARGS__中的参数列表被粘贴,每个参数之间用逗号分隔。这些逗号分别从左到右调用逗号运算符,后者将表达式转发到std::cerr。我通常使用一个自定义日志流来处理对文件、cerr、Windows的OutputDebugStringA或其他文件的写入。

在C++11编译器中使用可变模板,这将更像:

template <typename ...Ts>
void logger(Ts&&... argv) {
std::cerr << your_string_format_function(argv...);
}
void logger(const char* fmt) {
std::cerr << fmt;
}
void logger() {}
#define my_assert(condition, ...) do{ 
if (!(condition)) { 
logger(__VA_ARGS__); 
std::terminate(); 
} 
}while(false)

您需要一个实际使用可变参数的格式字符串函数,或者为boost::format之类的东西编写一个适配器。

如果你不介意缺乏类型安全性,你也可以使用printf