我希望能够自省一个c++类的名称,内容(即成员及其类型)等。这里我说的是原生c++,而不是托管c++,托管c++有反射。我意识到c++使用RTTI提供了一些有限的信息。哪些附加库(或其他技术)可以提供这些信息?


当前回答

我想宣传一下自动自省/反射工具包“IDK”的存在。它使用类似Qt的元编译器,并将元信息直接添加到目标文件中。据说它很容易使用。没有外部依赖。它甚至允许您自动反映std::string,然后在脚本中使用它。请看IDK

其他回答

反射本质上是关于编译器决定在运行时代码可以查询的代码中留下哪些足迹。c++以不为不用的东西付费而闻名;因为大多数人不使用/不想要反射,c++编译器通过不记录任何东西来避免成本。

因此,c++不提供反射,并且像其他答案所指出的那样,作为一般规则,自己“模拟”它并不容易。

在“其他技术”下,如果没有带有反射的语言,可以使用一个可以在编译时提取所需信息的工具。

我们的DMS软件再造工具包是通过显式语言定义参数化的通用编译器技术。它有语言定义C, c++, Java, COBOL, PHP,…

对于C、c++、Java和COBOL版本,它提供了对解析树和符号表信息的完整访问。符号表信息包括您可能希望从“反射”中获得的数据类型。如果您的目标是枚举一组字段或方法,并对它们做一些事情,DMS可以用于根据符号表中的内容以任意方式转换代码。

RareCpp库实现了相当简单和直观的反射——所有字段/类型信息都被设计成可以在数组中使用,或者感觉像是数组访问。它是为c++ 17编写的,可与Visual Studios、g++和Clang一起使用。这个库只有头文件,这意味着你只需要将“Reflect.h”复制到你的项目中就可以使用它。

被反射的结构体或类需要REFLECT宏,在该宏中您可以提供所反射的类的名称和字段的名称。

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(FuelTank, capacity, currentLevel, tickMarks)
};

这就是全部内容,不需要额外的代码来设置反射。可选地,您可以提供类和字段注释,以便能够遍历超类或向字段添加额外的编译时信息(例如Json::Ignore)。

遍历字段可以简单到…

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

您可以通过对象实例循环访问字段值(您可以读取或修改)和字段类型信息……

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

JSON库构建在RandomAccessReflection之上,它可以自动识别适当的JSON输出表示来读写,并且可以递归遍历任何反射字段,以及数组和STL容器。

struct MyOtherObject { int myOtherInt; REFLECT(MyOtherObject, myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(MyObject, myInt, myString, myOtherObject, myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

上面的代码可以这样运行……

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

参见……

反映文档 反映实现 更多用法示例

如果你像这样声明一个指向函数的指针:

int (*func)(int a, int b);

您可以像这样在内存中为该函数分配一个位置(需要libdl和dlopen)

#include <dlfcn.h>

int main(void)
{
    void *handle;
    char *func_name = "bla_bla_bla";
    handle = dlopen("foo.so", RTLD_LAZY);
    *(void **)(&func) = dlsym(handle, func_name);
    return func(1,2);
}

要使用间接方式加载局部符号,可以对调用二进制文件(argv[0])使用dlopen。

这样做的唯一要求(除了dlopen()、libdl和dlfcn.h)是知道函数的参数和类型。

看起来c++仍然没有这个特性。 c++ 11也有延迟反射(

搜索一些宏或者自己制作。Qt还可以帮助进行反射(如果可以使用的话)。

你想用反射做什么? 可以使用Boost类型特征和typeof库作为编译时反射的有限形式。也就是说,您可以检查和修改传递给模板的类型的基本属性。