C++中打印变量名的通用方法

14

给定一个类

struct {
  int a1;
  bool a2;
  ...
  char* a500;
  ...
  char a10000;      
}

我想要打印或者流式输出

"a1 value is SOME_VALUE"  
"a2 value is SOME_VALUE"
"a500 value is SOME_VALUE"
...
"a10000 value is SOME_VALUE"

成员变量的类型不同(主要是int、bool、char*等,即不需要重载<<运算符),而且成员变量的名称可以随意命名,即没有遵循任何规则。有没有通用的方法来避免一个一个地显式输入(非常繁琐和容易出错)?

感谢任何评论!


一些框架添加了反射,但这不是一种本地语言特性。 - spraff
在C++中,没有通用的方法来实现您想要的功能。您应该问自己是否正在使用正确的工具完成工作。考虑使用另一种语言或C++中的不同数据结构,例如vector或list。 - n. m.
此外,你真的做不好。以 struct { std::string a; } 为例 - 你真的想要它打印出 a._BUF[0] = 'H' a._BUF[1] = 'e' a._BUF[2] = 'l' , a._BUF[0] = 'l' 等等吗?还是你更想要 a = "Hello, world\n"?C++ 对于如何打印 struct Foo 有一个明确的想法,那就是使用 operator<<(std::ostream&, Foo); - MSalters
6个回答

21
你可以使用一个恶意宏:
#define DUMP(a) \
    do { std::cout << #a " is value " << (a) << std::endl; } while(false)

用法示例(编辑现已更新为结构成员示例):
#include <iostream>

#define DUMPSTR_WNAME(os, name, a) \
    do { (os) << (name) << " is value " << (a) << std::endl; } while(false)

#define DUMPSTR(os, a) DUMPSTR_WNAME((os), #a, (a))
#define DUMP(a)        DUMPSTR_WNAME(std::cout, #a, (a))

struct S {
    int a1;
    float a2;
    std::string a3;

    std::ostream& dump(std::ostream& os)
    {
        DUMPSTR(os, a1);
        DUMPSTR(os, a2);
        DUMPSTR(os, a3);
        return os;
    }
};

int main()
{
    S s = { 3, 3.14, "  03.1415926" };

    s.dump(std::cout);

    DUMP(s.a1);
    DUMP(s.a2);
    DUMP(s.a3);

    return 0;
}

查看实时{{link1:CodePad上的演示}}
a1 is value 3
a2 is value 3.14
a3 is value   03.1415926
s.a1 is value 3
s.a2 is value 3.14
s.a3 is value   03.1415926

为什么使用有趣的宏?
回答未提出的问题。考虑一下,如果你将宏调用嵌套在条件语句或循环中会发生什么。马歇尔·克莱恩解释了剩下的内容

是的,有时需要一个邪恶的宏来修补语言中的许多漏洞之一。 - David Hammen
你好,sehe,感谢你的回答。但是你仍然需要输入a1,a2,a3等等,这并没有比使用“printf(”a1...“); printf(”a2...“); ...“更普适或更好。如果我有一万个变量怎么办? - pepero
嗯...那么你要么是出了问题,要么是做错了什么。一个结构体里有10,000个变量:你应该使用容器。此外,看看序列化框架(它们仍然不打印成员名称,但我认为这是你的类的设计问题)。 - sehe
嗨,sehe,似乎没有通用的方法。我为您的宏点赞,但接受unwind的答案指出了反射。 :-) - pepero
“funny macro” 下面的链接似乎已经失效了。为什么它是“邪恶”的? - Keith M

11
你所寻找的功能通常称为reflection。它不是C++的一部分,因为在编译语言中,您需要的信息(可读性强的变量名)通常不会被编译器保留。它不需要运行代码,所以没有必要包含它。
调试器通常可以检查带外符号信息或二进制文件中保存的符号数据,以显示这些名称,但为此重新做可能比它值得的工作更多。
我建议寻找一些实现此功能的"技巧"(=解决方案)。

1
备受尊崇的pstruct命令解析gcc -g -s的输出以获得可用形式的信息。它旨在与perl一起使用,但您也可以将其用于此类方法。 - sehe

6
< p > watch宏是最有用的技巧之一。

#define watch(x) cout << (#x) << " is " << (x) << endl

如果您正在调试代码,watch(variable);将打印变量的名称和值。(这是可能的,因为它是在预处理期间构建的。)

1

这是不可能的(请参见其他答案)。

解决此问题的一个变通方法是使用自动代码生成。您可以在文件中编写字段定义,然后从中生成.h和.cpp文件。我曾经用过这种方法来处理大约100个类和大量字段的代码。它能够生成将它们发送到流(主要用于调试)和套接字通信的代码。它非常可靠(从未测试过这些功能之一),但由于它不是纯C++,因此可能不适合您的解决方案。


如果您(a)不想或无法使用外部库,以及(b)有太多的类/结构成员需要手动复制粘贴宏,那么这可能是最好的选择(sehe的答案)。 (或者如果结构/类代码经常更改,并且您担心某个人最终会犯复制粘贴错误。) - Keith M

0

在C++中,没有办法枚举类的成员,因为C++中没有反射。因此,您无法访问变量名。

不过,您可以使用成员指针...

void PrintMemberValue(int MyClass::*p, MyClass const & obj)
{
    cout << "Member has value " << obj.*p;
}

MyClass obj;
int MyClass::*p = &MyClass::a1;
PrintMemberValue(p, obj);
p = &MyClass::a2;
PrintMemberValue(p, obj);
etc

0

GDB可以打印结构体。该脚本生成GDB脚本以在由gdb_print位置指定的位置设置断点并打印值:

gdb-print-prepare()
{

    # usage:
    # mark print points with empty standalone defines:
    # gdb_print(huge_struct);
    # gdb-print-prepare $src > app.gdb
    # gdb --batch --quiet --command=app.gdb $app
    cat  <<-EOF
    set auto-load safe-path /
    EOF
    grep --with-filename --line-number --recursive '^\s\+gdb_print(.*);' $1 | \
    while IFS=$'\t ;()' read line func var rest; do
        cat  <<-EOF
        break ${line%:}
        commands
        silent
        where 1
        echo \\n$var\\n
        print $var
        cont
        end
        EOF
    done
    cat  <<-EOF
    run
    bt
    echo ---\\n
    EOF
}

来自https://gitlab.com/makelinux/lib/blob/master/snippets/gdb-print-prepare.md


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