提问人:Azeirah 提问时间:9/25/2015 最后编辑:M.MAzeirah 更新时间:7/13/2016 访问量:1503
访问宏中的变量值
Accessing variable values within a macro
问:
前段时间,我为 c 和 c++ 程序制作了这个漂亮的断言宏
#define ASSERT(truthy, message) \
if (!(truthy)) \
{\
cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << endl;\
}
在整个代码中分散 ASSERT 调用,每当值不真实时,它就会警告您!在开发过程中非常方便,可以提醒您潜在的错误。truthy
前任
ASSERT(filesFound > 0, "Couldn't find any files, check your path!");
当 filesFound 为 0 时,宏将打印出来
找不到任何文件,请检查您的路径!在文件中的第 27 行 openFiles.c. 检查文件是否找到> 0
现在,我希望它打印出来,为我提供更多相关信息,是传递到参数中的任何变量的值。喜欢这个truthy
找不到任何文件,请检查您的路径!在文件中的第 27 行 openFiles.c. 检查 filesFound > 0,filesFound 是否为 0
这似乎是类似 lisp 的领域,我想知道,是否有任何黑魔法 c 预处理可用于将变量和函数评估到它们的值,而无需评估语句?truthy
我想很失望。
答:
我想不出有什么办法可以做到这一点......除非通过传递另一个参数
#define ASSERT_PARAM(truthy, message, param) \
if (!(truthy)) \
{\
cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << ", value was " << param << endl;\
}
你可以这样使用它:
ASSERT_PARAM(filesFound > 0, "Couldn't find any files, check your path!", filesFound);
得到:
Couldn't find any files, check your path! on line 27 in file openFiles.c. Check was filesFound > 0, value was 0
评论
你要做的事情听起来非常复杂。恐怕在 C++ 中这是不可能的。
从技术上讲,您正在评估的是一个布尔表达式,因此您可以在断言失败时将其传递给解析器。然后,解析器将构建表达式树,获取叶子(表达式的元素)并返回它们。然后,应打印出返回的值。为此,您将需要对反射的支持,这在 C++ AFAIK 中实际上不受支持。
评论
也许不是梦寐以求的解决方案,但您可以将整个语句传递给宏。
#define ASSERT(trusty, action) if (!trusty) { action }
ASSERT(trusty, cout << a << b;)
ASSERT(trusty, printf("%d, %f\n", a, b);)
我认为您可以像他们在这里的第一个答案中所做的那样拆分表达式,然后您可以打印单个值。但我不确定它是否真的有效。truthy
然后可以使用可变参数模板功能对打印进行重新调整
也许您可以妥协,在断言表达式中只允许 2 个变量和 1 个运算符?如果是这样,您可以像这样制定临时解决方案:
#include <iostream>
#include <string>
#define STRINGIFY(x) #x
#define BIN_ASSERT(obj1, op, obj2, msg) \
if(!(obj1 op obj2)) \
{ \
std::cout << msg << " on line " << __LINE__ \
<< " in file " << __FILE__ \
<< "." << std::endl \
<< "Check was " \
<< STRINGIFY(obj1) STRINGIFY(op) STRINGIFY(obj2) \
<< "." << std::endl \
<< "Operator " << #obj1 << ": " << obj1 \
<< "." << std::endl \
<< "Operator " << #obj2 << ": " << obj2 \
<< "." << std::endl; \
}
int main (void)
{
int x = 2;
int y = 3;
std::string s1 = "hello";
std::string s2 = "world";
BIN_ASSERT(1, +, -1, "Value zero"); std::cout << std::endl;
BIN_ASSERT(x, ==, y, "Numbers not equal"); std::cout << std::endl;
BIN_ASSERT(s1, ==, s2, "Strings not equal"); std::cout << std::endl;
}
输出:
Value zero on line 30 in file test.c.
Check was 1+-1.
Operator 1: 1.
Operator -1: -1.
Numbers not equal on line 31 in file test.c.
Check was x==y.
Operator x: 2.
Operator y: 3.
Strings not equal on line 32 in file test.c.
Check was s1==s2.
Operator s1: hello.
Operator s2: world.
我想知道让宏接收消息是否真的那么有用。失败的断言是向开发人员发送的消息,表明代码中存在导致异常行为或使程序处于不可接受的状态的 bug。用户与它的关系较小(如果他们甚至可以访问源代码)。
下面的代码定义了一个宏,该宏采用布尔表达式,对其进行计算并打印信息性消息。该消息包含一个值,您要求在断言失败时检查该值。ASSERT
该宏,就像标准宏 (in ) 继续调用 (from ) 以导致异常程序终止一样。这就是你想要的,因为程序进入了一种状态,在这种状态下,它不知道该做什么。assert()
<cassert>
abort()
<cstdlib>
为了简洁起见,我在这里使用。你想做什么就做什么。std::printf()
#include <cstdlib>
#include <cstdio>
#define ASSERT(value, inspect) \
if (!(value)) { \
std::printf("ASSERTION FAILED: '%s', %s is %d: %s@%s:%d\n", #value, \
#inspect, inspect, __func__, __FILE__, __LINE__); \
abort(); \
}
int foo() { return 42; }
int main()
{
// ...
ASSERT(foo() - 40 == 1, foo());
//...
}
程序运行:
$ ./a.out
ASSERTION FAILED: 'foo() - 40 == 1', foo() is 42: [email protected]:16
Abort
如果不向宏添加更多参数,就不可能完全按照您的要求执行操作。在某些时候,你必须停下来,意识到你正在花时间创建一个你不想看到的文本字符串。
您需要构建一个表达式“grabber”/builder。
宏将变成如下内容:
#define ASSERT_PARAM(truthy, message, param) \
if (!(truthy)) \
{\
Grabber g;
g << #truthy; // grab expression as string
g % truthy; // grab expression and values
cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << ", value was " << param << endl;\
cout << g; \
}
Grabber 是做什么的?
这是一堆疯狂的 C++ 构建了一个表达式。它会使每个运算符过载以“抓取”运算符的参数。每个运算符都返回对抓取器的引用,以便它可以抓取下一个运算符。即
Grabber g;
g % filesFound > 0;
由于 %(以及 * 和 /)具有很高的优先级,因此上面的解析如下:
((g % filesFound) > 0)
如果只是记录(或打印)传入的值(即 filesFound),并且 - 重要的是 - 返回自身 (g),以便它成为下一个表达式的一部分:即它变为 g > 0。导致被调用,> 0 被记录。template<typename T> Grabber::operator%(T const & val)
template<typename T> Grabber::operator>(T const & val)
然后可以喷出所有抓住的东西。cout << g
如上所述,“这是可能的——Catch 库做到了。但这非常困难”。
P.S. 你应该把你的宏包装在一个 do ...而 0 像这样:
#define ASSERT_PARAM(truthy, message, param) \
do \
{ \
if (!(truthy)) \
{\
cout << message << " on line " << __LINE__ << " in file " << __FILE__ << ". Check was " << #truthy << ", value was " << param << endl;\
cout << g; \
} \
} while (0)
您目前所拥有的意味着这是有效代码:
ASSERT(foo != 0)
else
{
}
这不是有效的代码:
if (foo != nullptr)
ASSERT(foo->bar != nullptr);
else
x = 10;
评论
g % truthy;
truthy
truthy
filesFound > 0
g % (filesFound > 0)
(g % filesFound) > 0
Grabber
%
g % (filesFound > 0)
filesFound > 0
g
令人惊讶的是,我之前解决过类似的问题,但我不确定在这种情况下它是否能帮助你。
最初的解决方案是由安德烈·亚历山德莱斯库(Andrei Alexandrescu)在《增强断言》一文中提出的,毫无疑问,它依赖于一些宏观技巧。
这个惊人的设施可以用作以下用途:
string s1, s2;
...
SMART_ASSERT(s1.empty() && s2.empty())(s1)(s2);
如果出现问题,将显示该消息
Assertion failed in matrix.cpp: 879412:
Expression: 's1.empty() && s2.empty()'
Values: s1 = "Wake up, Neo"
s2 = "It's time to reload."
需要注意的是,从理论上讲,SMART_ASSERT可以捕获无限的变量。
有关实现细节,请查看文章。
我一直使用的另一种解决方案是在宏中支持 varargs,然后强制断言用户指定相关的消息/变量 - 每次都需要一些额外的工作,但从好的方面来说,您可以获得您想要的格式,并包含“真实”位中不可用的信息, 例如:
#define ASSERT(truthy, message, ...) \
if (!(truthy)) \
{\
MyAssertHandler(__LINE__, __FILE__, #truthy, message, ##__VA_ARGS__);
}
那么你的处理程序只是一个相当标准的 var-arg 函数,可以使用例如 vsnprintf 来生成消息并输出它,例如在我的头顶上:
void MyAssertHandler(int line, const char* file, const char* expressionStr, const char* format, ...)
{
// Note: You probably want to use vsnprintf instead to first generate
// the message and then add extra info (line, filename, etc.) to
// the actual output
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
// Log to bug database, DebugBreak() if a debugger is attached, etc.
}
用法:
ASSERT(IsBlah(), "BlahBlah: x = %.2f, name = %s", GetX(), GetName());
评论
do{ ... }while(0);
do
while
assert
assert(truthy && "The foo was barred!");
do {…} while (false)
do{ int val = ... }while(0);
val
do…while
if