我希望能够自省一个c++类的名称,内容(即成员及其类型)等。这里我说的是原生c++,而不是托管c++,托管c++有反射。我意识到c++使用RTTI提供了一些有限的信息。哪些附加库(或其他技术)可以提供这些信息?
当前回答
你可以在这里找到另一个库:http://www.garret.ru/cppreflection/docs/reflect.html 它支持两种方式:从调试信息中获取类型信息和让程序员提供该信息。
我也对反思我的项目感兴趣,发现了这个库,我还没有尝试过,但尝试了这个家伙的其他工具,我喜欢他们的工作方式:-)
其他回答
如果你正在寻找相对简单的c++反射——我从各种来源的宏/定义中收集了它们,并注释了它们的工作方式。你可以下载页眉 这里的文件:
https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h
一组定义,加上它上面的功能:
https://github.com/tapika/TestCppReflect/blob/master/CppReflect.h https://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpp https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h
示例应用程序驻留在git存储库以及,在这里: https://github.com/tapika/TestCppReflect/
我将部分复制在这里并进行解释:
#include "CppReflect.h"
using namespace std;
class Person
{
public:
// Repack your code into REFLECTABLE macro, in (<C++ Type>) <Field name>
// form , like this:
REFLECTABLE( Person,
(CString) name,
(int) age,
...
)
};
void main(void)
{
Person p;
p.name = L"Roger";
p.age = 37;
...
// And here you can convert your class contents into xml form:
CStringW xml = ToXML( &p );
CStringW errors;
People ppl2;
// And here you convert from xml back to class:
FromXml( &ppl2, xml, errors );
CStringA xml2 = ToXML( &ppl2 );
printf( xml2 );
}
REFLECTABLE定义使用类名+字段名+偏移量-来标识特定字段位于内存中的哪个位置。我已经尽可能地学习。net术语,但是c++和c#是不同的,所以不是一对一的。整个c++反射模型驻留在TypeInfo和FieldInfo类中。
我已经使用pugi xml解析器获取演示代码到xml并从xml恢复回来。
演示代码的输出如下所示:
<?xml version="1.0" encoding="utf-8"?>
<People groupName="Group1">
<people>
<Person name="Roger" age="37" />
<Person name="Alice" age="27" />
<Person name="Cindy" age="17" />
</people>
</People>
也可以通过TypeTraits类和部分模板规范来启用任何第三方类/结构支持-以类似于CString或int的方式定义自己的TypeTraitsT类-参见中的示例代码
https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195
该解决方案适用于Windows / Visual studio。它可以移植到其他操作系统/编译器,但还没有这样做。(如果你真的喜欢解决方案,请问我,我可能会帮助你)
该解决方案适用于一个类和多个子类的一次序列化。
然而,如果你正在寻找序列化类部分的机制,甚至是控制反射调用产生的功能,你可以看看下面的解决方案:
https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel
更详细的信息可以从youtube视频中找到:
c++运行时类型反射 https://youtu.be/TN8tJijkeFE
我试图更深入地解释c++反射是如何工作的。
示例代码如下所示:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/testCppApp.cpp
c.General.IntDir = LR"(obj\$(ProjectName)_$(Configuration)_$(Platform)\)";
c.General.OutDir = LR"(bin\$(Configuration)_$(Platform)\)";
c.General.UseDebugLibraries = true;
c.General.LinkIncremental = true;
c.CCpp.Optimization = optimization_Disabled;
c.Linker.System.SubSystem = subsystem_Console;
c.Linker.Debugging.GenerateDebugInformation = debuginfo_true;
但是这里的每一步实际上都会导致函数调用 使用c++属性__declspec(property(get =, put…).
它以路径的形式接收有关c++数据类型、c++属性名和类实例指针的全部信息,并根据这些信息生成xml、json,甚至在互联网上序列化这些信息。
这样的虚拟回调函数的例子可以在这里找到:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp
参见函数ReflectCopy和虚函数::OnAfterSetProperty。
但是因为这个话题很高级,我建议先看视频。
如果您有一些改进的想法,请随时与我联系。
在我的c++生涯中,我知道的两个类似反射的解决方案是:
1)使用RTTI,如果你能够从一个“对象”基类派生所有的类,它将为你提供一个引导来构建类似反射的行为。该类可以提供一些方法,如GetMethod, GetBaseClass等。至于这些方法是如何工作的,你需要手动添加一些宏来装饰你的类型,这些宏在幕后创建类型的元数据,为GetMethods等提供答案。
2)如果你可以访问编译器对象,另一个选择是使用DIA SDK。如果我没记错的话,这允许您打开pdbs,其中应该包含c++类型的元数据。也许足够做你想做的事了。例如,本页展示了如何获取类的所有基类型。
这两种解决方案都有点难看!没有什么比c++更能让你欣赏c#的奢华了。
祝你好运。
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 ]
}
参见……
反映文档 反映实现 更多用法示例
你想用反射做什么? 可以使用Boost类型特征和typeof库作为编译时反射的有限形式。也就是说,您可以检查和修改传递给模板的类型的基本属性。
您需要查看您正在尝试做什么,以及RTTI是否满足您的需求。我已经实现了自己的伪反射,用于某些非常特定的目的。例如,我曾经希望能够灵活地配置模拟输出内容。它需要在输出的类中添加一些样板代码:
namespace {
static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
}
bool MyObj::BuildMap()
{
Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
return true;
}
第一个调用将该对象添加到筛选系统,该系统调用BuildMap()方法以确定哪些方法可用。
然后,在配置文件中,你可以这样做:
FILTER-OUTPUT-OBJECT MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1 person == 1773
FILTER-CLAUSE-2 time > 2000
通过一些涉及boost的模板魔法,可以在运行时(读取配置文件时)将其转换为一系列方法调用,因此相当高效。我不建议你这样做,除非你真的需要,但是,当你这样做的时候,你可以做一些非常酷的事情。