提问人:Nick 提问时间:9/3/2008 最后编辑:Aakash GoelNick 更新时间:11/13/2023 访问量:281474
如何向 C++ 应用程序添加反射?
How can I add reflection to a C++ application?
问:
我希望能够自省 C++ 类的名称、内容(即成员及其类型)等。我在这里说的是原生C++,而不是托管C++,它有反射。我意识到C++使用RTTI提供了一些有限的信息。哪些其他库(或其他技术)可以提供此信息?
答:
我想要一匹小马,但小马不是免费的。:-p
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI 这就是你将要得到的。像你正在考虑的反射 - 在运行时可用的完全描述性元数据 - 默认情况下C++不存在。
评论
你想用反思做什么?
您可以将 Boost 类型特征和 typeof 库用作编译时反射的有限形式。也就是说,您可以检查和修改传递给模板的类型的基本属性。
我从C++时代就知道的两个类似反射的解决方案是:
1)使用RTTI,如果你能够让你的所有类都派生自一个“对象”基类,它将为你提供一个引导程序来构建你的反射行为。该类可以提供一些方法,如 GetMethod、GetBaseClass 等。至于这些方法的工作原理,您需要手动添加一些宏来修饰您的类型,这些宏在幕后创建类型中的元数据以提供对 GetMethods 等的答案。
2) 如果您有权访问编译器对象,另一种选择是使用 DIA SDK。如果我没记错的话,这可以让你打开pdbs,它应该包含你的C++类型的元数据。做你需要的事情可能就足够了。例如,本页显示如何获取类的所有基类型。
不过,这两种解决方案都有点丑陋!没有什么比一点C++更能让你欣赏C#的奢侈了。
祝你好运。
评论
我建议使用Qt。
有一个开源许可证和一个商业许可证。
评论
你需要看看你要做什么,以及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
评论
我想你可能会发现Dominic Filion的文章“在C++中使用反射模板”很有趣。它位于 Game Programming Gems 5 的第 1.4 节中。不幸的是,我没有随身携带我的副本,但请寻找它,因为我认为它解释了您的要求。
周围有两种游泳。reflection
- 通过循环访问类型的成员、枚举其方法等进行检查。
这在 C++ 中是不可能的。 - 通过检查类类型(类、结构、联合)是否具有方法或嵌套类型来检查是否派生自另一个特定类型。
这种事情可以通过C++使用.将boost::type_traits
用于许多事情(例如检查类型是否为整数)。要检查成员函数是否存在,请使用模板化检查是否存在类成员函数?。要检查是否存在某种嵌套类型,请使用 plain SFINAE 。template-tricks
如果你正在寻找完成 1) 的方法,比如查看一个类有多少个方法,或者喜欢获取类 ID 的字符串表示,那么恐怕没有标准的 C++ 方法可以做到这一点。您必须使用其中任何一个
- 一个元编译器,如Qt Meta Object Compiler,它可以翻译你的代码,添加额外的元信息。
- 一个由宏组成的框架,允许您添加所需的元信息。你需要告诉框架所有的方法、类名、基类和它需要的一切。
C++ 在制造时考虑到了速度。如果你想要像 C# 或 Java 那样进行高级检查,那么如果不做一些额外的努力,就没有办法做到这一点。
评论
members<T>
members<T>(T&)
我曾经做过类似你所追求的事情,虽然可以获得一定程度的反思和访问更高级别的功能,但维护头痛可能不值得。我的系统用于通过类似于 Objective-C 的消息传递和转发概念的委派将 UI 类与业务逻辑完全分离。这样做的方法是创建一些能够将符号映射的基类(我使用了一个字符串池,但如果你更喜欢速度和编译时错误处理而不是完全的灵活性,你可以用枚举来做到这一点)到函数指针(实际上不是纯粹的函数指针,而是类似于 Boost 对 Boost.Function 的东西——我当时没有访问权限)。您可以对成员变量执行相同的操作,只要您有一些能够表示任何值的公共基类。整个系统是键值编码和委托的毫不掩饰的剽窃,有一些副作用,也许值得花费大量时间让使用该系统的每个类将其所有方法和成员与合法调用相匹配: 1)任何类都可以在任何其他类上调用任何方法,而不必包含标头或编写假基类,因此接口可以预定义编译器;2)成员变量的getter和setter很容易使线程安全,因为更改或访问它们的值总是通过所有对象的基类中的2个方法来完成的。
这也导致了做一些非常奇怪的事情的可能性,否则在C++中并不容易。例如,我可以创建一个包含任意类型的项(包括自身)的 Array 对象,并通过向所有数组项传递消息并收集返回值来动态创建新数组(类似于 Lisp 中的 map)。另一个是键值观察的实现,通过这种实现,我能够将 UI 设置为立即响应后端类成员的变化,而不是不断轮询数据或不必要地重新绘制显示。
也许您更感兴趣的是,您还可以转储为类定义的所有方法和成员,并且以字符串形式转储。
系统的缺点可能会阻止您打扰:添加所有消息和键值非常繁琐;它比没有任何反射慢;你会变得讨厌看到和遍布你的代码库,并充满暴力的热情;强类型系统的局限性仍然存在,您实际上只是将它们隐藏了一点,因此不那么明显。字符串中的错别字也不是一个有趣或容易发现的惊喜。boost::static_pointer_cast
boost::dynamic_pointer_cast
至于如何实现这样的事情:只需使用指向某个公共基的共享和弱指针(我的指针非常有想象力地称为“对象”),并派生您想要使用的所有类型。我建议安装 Boost.Function,而不是像我那样做,它使用一些自定义废话和大量丑陋的宏来包装函数指针调用。由于一切都是映射的,因此检查对象只需遍历所有键即可。由于我的课程基本上尽可能接近仅使用 C++ 直接抄袭 Cocoa,如果您想要这样的东西,那么我建议使用 Cocoa 文档作为蓝图。
评论
信息确实存在 - 但不是您需要的格式,并且仅在您导出类时。这在 Windows 中有效,我不知道其他平台。例如,使用 storage-class 说明符:
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 流式传输等非常有用,并且无需从特定的基类派生。如果有足够的需求,也许我们可以把它敲成形状以供发布。
评论
__declspec(dllexport)
当我想在C++中进行反思时,我阅读了这篇文章并改进了我在那里看到的内容。对不起,没有可以。我不拥有结果......但你当然可以得到我所拥有的,然后从那里开始。
我目前正在研究,当我愿意的时候,使用inherit_linearly的方法,使可反射类型的定义变得更加容易。实际上我已经走了很远,但我还有很长的路要走。C++0 的变化很可能在这方面有很大帮助。
编辑:CAMP不再维护;有两个分叉可用:
CAMP 是麻省理工学院许可的库(以前称为 LGPL),它为 C++ 语言添加了反射。它不需要在编译中执行特定的预处理步骤,但必须手动进行绑定。
目前的 Tegesoft 库使用 Boost,但还有一个使用 C++11 的分支不再需要 Boost。
这个问题现在有点老了(不知道为什么我今天总是遇到老问题),但我正在考虑引入编译时反思BOOST_FUSION_ADAPT_STRUCT。
当然,由你来映射到运行时反射,这不会太容易,但在这个方向上是可能的,而反之则不然:)
我真的认为封装宏可以生成获取运行时行为的必要方法。BOOST_FUSION_ADAPT_STRUCT
评论
反射实质上是关于编译器决定在运行时代码可以查询的代码中留下哪些内容作为封装。C++ 以不为你不使用的东西付费而闻名;因为大多数人不使用/想要反射,所以 C++ 编译器通过不记录任何内容来避免成本。
因此,C++不提供反射,并且像其他答案所指出的那样,自己“模拟”它作为一般规则并不容易。
在“其他技术”下,如果你没有一种具有反射功能的语言,请获取一个可以在编译时提取所需信息的工具。
我们的 DMS 软件再造工具包是通用编译器技术,通过显式语言定义进行参数化。它有 C、C++、Java、COBOL、PHP 等语言定义
对于 C、C++、Java 和 COBOL 版本,它提供了对解析树和符号表信息的完整访问。该符号表信息包括您可能希望从“反射”中获得的数据类型。如果您的目标是枚举一组字段或方法并对它们执行某些操作,则可以使用 DMS 根据您在符号表中以任意方式找到的内容转换代码。
你可以在这里找到另一个库: http://www.garret.ru/cppreflection/docs/reflect.html 它支持两种方式:从调试信息中获取类型信息,并让程序员提供此信息。
我也对我的项目的反思感兴趣,并找到了这个库,我还没有尝试过,但尝试了这个人的其他工具,我喜欢它们的工作方式:-)
看起来C++仍然没有这个功能。 C++11 也推迟了反射((
搜索一些宏或创建自己的宏。Qt也可以帮助反射(如果可以使用的话)。
您需要做的是让预处理器生成有关字段的反射数据。此数据可以存储为嵌套类。
首先,为了使在预处理器中编写它更容易、更干净,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。所以与其写,不如写.以下是一些方便的宏,可帮助处理类型化表达式:int x
(int) x
#define REM(...) __VA_ARGS__
#define EAT(...)
// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x
接下来,我们定义一个宏来生成有关每个字段(以及字段本身)的数据。此宏将按如下方式调用:REFLECTABLE
REFLECTABLE
(
(const char *) name,
(int) age
)
因此,使用 Boost.PP,我们遍历每个参数并生成如下数据:
// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
typedef T type;
};
template<class M, class T>
struct make_const<const M, T>
{
typedef typename boost::add_const<T>::type type;
};
#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
Self & self; \
field_data(Self & self) : self(self) {} \
\
typename make_const<Self, TYPEOF(x)>::type & get() \
{ \
return self.STRIP(x); \
}\
typename boost::add_const<TYPEOF(x)>::type & get() const \
{ \
return self.STRIP(x); \
}\
const char * name() const \
{\
return BOOST_PP_STRINGIZE(STRIP(x)); \
} \
}; \
这样做的目的是生成一个常量,即类中可反射字段的数量。然后它专门针对每个领域。它还与类成为好友,这样它就可以访问字段,即使它们是私有的:fields_n
field_data
reflector
struct reflector
{
//Get field_data at index N
template<int N, class T>
static typename T::template field_data<N, T> get_field_data(T& x)
{
return typename T::template field_data<N, T>(x);
}
// Get the number of fields
template<class T>
struct fields
{
static const int n = T::fields_n;
};
};
现在,为了遍历字段,我们使用 visitor 模式。我们创建一个从 0 到字段数的 MPL 范围,并访问该索引处的字段数据。然后,它将字段数据传递给用户提供的访客:
struct field_visitor
{
template<class C, class Visitor, class I>
void operator()(C& c, Visitor v, I)
{
v(reflector::get_field_data<I::value>(c));
}
};
template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}
现在,在关键时刻,我们把它们放在一起。以下是我们如何定义一个可反映的类:Person
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
下面是一个使用反射数据对字段进行迭代的广义函数:print_fields
struct print_visitor
{
template<class FieldData>
void operator()(FieldData f)
{
std::cout << f.name() << "=" << f.get() << std::endl;
}
};
template<class T>
void print_fields(T & x)
{
visit_each(x, print_visitor());
}
将 与 反射类一起使用的示例:print_fields
Person
int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
其中输出:
name=Tom
age=82
瞧,我们刚刚在不到 100 行代码中用 C++ 实现了反射。
评论
#define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuple
#define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__))
#define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
开箱即用的 C++ 不支持反射。这是可悲的,因为它使防御测试成为一种痛苦。
有几种方法可以进行反思:
第一个链接看起来最有前途(使用 mod 来叮当),第二个讨论了许多技术,第三个是使用 gcc 的不同方法:
现在有一个C++反射工作组。查看 C++ 14 @ CERN 的新闻:
编辑 13/08/17:
自最初的帖子以来,反思方面已经取得了许多潜在的进展。下面提供了有关各种技术和状态的更多详细信息和讨论:
然而,在不久的将来,C++ 中的标准化反射方法看起来并不乐观,除非社区对支持 C++ 中的反射有更多的兴趣。
下面根据上次 C++ 标准会议的反馈详细介绍了当前状态:
编辑 2017/12/13
反射看起来正在向 C++ 20 或更高,可能是 TSR。然而,移动速度很慢。
编辑 15/09/2018
TS草案已送交国家机构投票。
文本可以在这里找到:https://github.com/cplusplus/reflection-ts
编辑 2019/11/07
反思 TS 功能已完成,并在夏季(2019 年)征求意见和投票。
元模板编程方法将被更简单的编译时代码方法所取代(未反映在 TS 中)。
编辑 2020/10/02
此处请求支持 Visual Studio 中的反射 TS:There is a request to support the reflection TS in Visual Studio here:
作者大卫·桑克尔 (David Sankel) 关于 TS 的演讲:
编辑 2020 年 3 月 17 日
反思工作正在取得进展。“2020-02布拉格ISO C++委员会旅行报告”的报告可以在这里找到:
有关 C++23 正在考虑的内容的详细信息可以在这里找到(包括关于反射的简短部分):
编辑 2020 年 6 月 4 日
Jeff Preshing 发布了一个名为“胶合板”的新框架,其中包含运行时反射机制。更多细节可以在这里找到:
这些工具和方法看起来是迄今为止最完善和最容易使用的。
编辑:2020 年 7 月 12 日
叮当实验反射叉:https://github.com/lock3/meta/wiki
有趣的反射库,它使用 clang 工具库来提取信息以进行简单的反射,而无需添加宏: https://github.com/chakaz/reflang
编辑 Feb 24, 2021
其他一些叮当工具方法:
编辑 Aug 25, 2021
在 youtube 上在线的 ACCU 演讲也非常值得一听 https://www.youtube.com/watch?v=60ECEc-URP8 它谈到了当前对标准的提案和基于 clang 的实现。
看:
- https://github.com/lock3/meta,分纸/P2320
- 编译器资源管理器:https://cppx.godbolt.org/ 将 p2320 中继用于编译器版本。
编辑:2023 年 5 月 31 日感谢 @ilciavo 指出这个库。
Boost Describe 看起来是一种有趣的反思方法。它是宏和具有良好文档的体面支持库的组合。
看:
编辑 2023 年 11 月 13 日
在上一次C++会议上,Reflection似乎有动静,正如Herb Sutter所报道的那样:
引用的论文在这里:
所以看起来我们可能会在 C++26 中得到静态反射。
评论
尽管 C++ 不支持开箱即用的反射,但实现起来并不难。 我遇到了这篇很棒的文章:http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
本文非常详细地解释了如何实现一个非常简单和基本的反射系统。当然,这不是最健康的解决方案,而且还有粗糙的边缘需要解决,但对于我的需求来说,这已经足够了。
底线 - 如果正确完成反射可以得到回报,这在 C++ 中是完全可行的。
查看 Classdesc http://classdesc.sf.net。它以类“描述符”的形式提供反射,适用于任何标准的 C++ 编译器(是的,众所周知,它可以与 Visual Studio 和 GCC 一起使用),并且不需要源代码注释(尽管存在一些编译指示来处理棘手的情况)。它已经开发了十多年,并用于许多工业规模的项目。
评论
编辑:截至2017年2月7日更新了断开的链接。
我想没有人提到这一点:
在CERN,他们使用C++的全反射系统:
欧洲核子研究中心反射。它似乎效果很好。
评论
我想宣传自动内省/反思工具包“IDK”的存在。它使用像 Qt 这样的元编译器,并将元信息直接添加到目标文件中。据称它易于使用。没有外部依赖关系。它甚至允许您自动反映 std::string,然后在脚本中使用它。请看IDK
C++ 中还有另一个用于反射的新库,称为 RTTR(运行时类型反射,另请参阅 github)。
该接口类似于 C# 中的反射,无需任何 RTTI 即可工作。
Ponder 是一个 C++ 反射库,用于回答这个问题。我考虑了这些选项并决定自己制作,因为我找不到一个符合我所有条件的选项。
虽然这个问题有很好的答案,但我不想使用大量的宏,也不想依赖 Boost。Boost是一个很棒的库,但是有很多小型定制的C++0项目,它们更简单,编译时间更快。能够在外部修饰类也有好处,比如包装一个不支持 C++ 11 的 C++ 库。它是 CAMP 的分支,使用 C++11,不再需要 Boost。
C++中的反射非常有用,在这种情况下,您需要为每个成员运行一些方法(例如:序列化,哈希,比较)。我提供了通用解决方案,语法非常简单:
struct S1
{
ENUMERATE_MEMBERS(str,i);
std::string str;
int i;
};
struct S2
{
ENUMERATE_MEMBERS(s1,i2);
S1 s1;
int i2;
};
其中 ENUMERATE_MEMBERS 是一个宏,稍后将介绍 (UPDATE):
假设我们已经为 int 和 std::string 定义了序列化函数,如下所示:
void EnumerateWith(BinaryWriter & writer, int val)
{
//store integer
writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
//store string
writer.WriteBuffer(val.c_str(), val.size());
}
我们在“秘密宏”附近有通用函数;)
template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}
现在你可以写
S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");
EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)
因此,在结构定义中ENUMERATE_MEMBERS宏,您可以在不触及原始类型的情况下构建序列化、比较、哈希和其他东西,唯一的要求是为每个类型实现“EnumerateWith”方法,该方法不可枚举,每个枚举器(如 BinaryWriter)。通常,您必须实现 10-20 种“简单”类型来支持项目中的任何类型。
这个宏在运行时应该有零开销来创建/破坏结构,并且 T.EnumerateWith() 的代码应该按需生成,这可以通过使其成为模板内联函数来实现,所以所有故事中唯一的开销是向每个结构添加 ENUMERATE_MEMBERS(m1,m2,m3...),而在任何解决方案中都必须为每个成员类型实现特定方法, 所以我不认为它是开销。
更新: ENUMERATE_MEMBERS宏的实现非常简单(但是可以稍微扩展一下以支持从可枚举结构继承)
#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }
// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v)
{
int x[] = { (EnumerateWith(enumerator, v), 1)... };
}
// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
val.EnumerateWith(enumerator);
}
而且您不需要任何第三方库来获取这 15 行代码;)
如果声明指向如下函数的指针:
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);
}
要使用间接加载本地符号,可以在调用二进制文件 () 上使用 。dlopen
argv[0]
唯一的要求(除了 、 和 )是知道函数的参数和类型。dlopen()
libdl
dlfcn.h
如果您正在寻找相对简单的 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
define 使用类名 + 字段名和 - 来标识特定字段在内存中的哪个位置。我试图尽可能地学习 .NET 术语,但 C++ 和 C# 是不同的,所以它不是 1 对 1。整个 C++ 反射模型驻留在 和 类中。offsetof
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 甚至通过 Internet 序列化该信息。
可以在此处找到此类虚拟回调函数的示例:
https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp
请参阅 函数 和 虚函数 。ReflectCopy
::OnAfterSetProperty
但由于主题真的很高级 - 我建议先通过视频查看。
如果您有一些改进想法,请随时与我联系。
您可以使用 Boost::Hana 库中的BOOST_HANA_DEFINE_STRUCT为结构体实现很酷的静态反射功能。
Hana 的用途非常广泛,不仅适用于您心目中的用例,还适用于许多模板元编程。
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)。
遍历字段可以像...
RareTs::Members<FuelTank>::forEach([&](auto member) {
std::cout << member.name << std::endl;
});
您可以包含实例/获取值和访问类型信息:
RareTs::Members<FuelTank>::forEach(fuelTank, [](auto member, auto & value) {
using MemberType = typename decltype(member)::type;
std::cout << RareTs::toStr<MemberType>() << " " << member.name
<< ": " << value << std::endl;
});
JSON 库建立在 RareCpp 之上,它会自动识别适当的 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 ]
}
另请参阅...
下一个:如何打印出矢量的内容?
评论