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


当前回答

这些信息确实存在——但不是你需要的格式,而且只有当你导出你的类时。这适用于Windows,我不知道其他平台。使用存储类说明符,例如:

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}

这使得编译器将类定义数据构建到DLL/Exe中。但它不是一种可以用于反射的格式。

在我的公司,我们建立了一个解释元数据的库,允许你在不插入额外的宏等到类本身的情况下反映一个类。它允许如下方式调用函数:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);

这有效地做到:

instance_ptr->Foo(1.331);

Invoke(this_pointer,…)函数有可变参数。显然,通过这样调用函数,你可以绕过诸如const-safety之类的东西,所以这些方面是作为运行时检查实现的。

我相信语法可以改进,到目前为止它只适用于Win32和Win64。我们发现它非常有用,可以为类提供自动GUI接口,在c++中创建属性,流到XML和从XML输出等等,而且不需要从特定的基类派生。如果有足够的需求,也许我们可以把它做成样子发布。

其他回答

你可以在这里找到另一个库:http://www.garret.ru/cppreflection/docs/reflect.html 它支持两种方式:从调试信息中获取类型信息和让程序员提供该信息。

我也对反思我的项目感兴趣,发现了这个库,我还没有尝试过,但尝试了这个家伙的其他工具,我喜欢他们的工作方式:-)

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

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

这个问题现在有点老了(不知道为什么我今天一直在问老问题),但我在想BOOST_FUSION_ADAPT_STRUCT,它引入了编译时反射。

当然,这取决于你将其映射到运行时反射,这不会太容易,但在这个方向上是可能的,而不是在相反的方向上:)

我真的认为一个宏来封装BOOST_FUSION_ADAPT_STRUCT可以生成必要的方法来获得运行时行为。

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 ]
}

参见……

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

I did something like what you're after once, and while it's possible to get some level of reflection and access to higher-level features, the maintenance headache might not be worth it. My system was used to keep the UI classes completely separated from the business logic through delegation akin to Objective-C's concept of message passing and forwarding. The way to do it is to create some base class that is capable of mapping symbols (I used a string pool but you could do it with enums if you prefer speed and compile-time error handling over total flexibility) to function pointers (actually not pure function pointers, but something similar to what Boost has with Boost.Function--which I didn't have access to at the time). You can do the same thing for your member variables as long as you have some common base class capable of representing any value. The entire system was an unabashed ripoff of Key-Value Coding and Delegation, with a few side effects that were perhaps worth the sheer amount of time necessary to get every class that used the system to match all of its methods and members up with legal calls: 1) Any class could call any method on any other class without having to include headers or write fake base classes so the interface could be predefined for the compiler; and 2) The getters and setters of the member variables were easy to make thread-safe because changing or accessing their values was always done through 2 methods in the base class of all objects.

It also led to the possibility of doing some really weird things that otherwise aren't easy in C++. For example I could create an Array object that contained arbitrary items of any type, including itself, and create new arrays dynamically by passing a message to all array items and collecting the return values (similar to map in Lisp). Another was the implementation of key-value observing, whereby I was able to set up the UI to respond immediately to changes in the members of backend classes instead of constantly polling the data or unnecessarily redrawing the display.

也许您更感兴趣的是,您还可以转储为类定义的所有方法和成员,而且是字符串形式。

该系统的缺点可能会让您望而却步:添加所有消息和键值非常繁琐;它比没有反射要慢;你会讨厌看到boost::static_pointer_cast和boost::dynamic_pointer_cast遍布你的代码库;强类型系统的局限性仍然存在,您实际上只是将它们隐藏了一些,所以它不那么明显。字符串中的错别字也不是一个有趣或容易发现的惊喜。

As to how to implement something like this: just use shared and weak pointers to some common base (mine was very imaginatively called "Object") and derive for all the types you want to use. I'd recommend installing Boost.Function instead of doing it the way I did, which was with some custom crap and a ton of ugly macros to wrap the function pointer calls. Since everything is mapped, inspecting objects is just a matter of iterating through all of the keys. Since my classes were essentially as close to a direct ripoff of Cocoa as possible using only C++, if you want something like that then I'd suggest using the Cocoa documentation as a blueprint.