如何向c++应用程序添加反射?

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

265676 次浏览

我也想要一匹小马,但小马不是免费的。: - 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++中使用反射模板”这篇文章感兴趣。它在游戏编程要点5的1.4节中。不幸的是,我没有带我的副本,但你可以找找看,因为我认为它解释了你想要的东西。

有两种reflection在游动。

  1. 通过迭代一个类型的成员来检查,枚举它的方法等等。

    这在c++中是不可能的。李< / >
  2. 检查类类型(class, struct, union)是否具有方法或嵌套类型,是否派生自另一个特定类型。

    这类事情在c++中使用template-tricks是可能的。使用boost::type_traits做很多事情(比如检查类型是否为整型)。要检查成员函数是否存在,请使用模板检查类成员函数的存在?。要检查某个嵌套类型是否存在,使用普通的SFINAE .

如果你想要实现1),比如查看一个类有多少个方法,或者获取一个类id的字符串表示形式,那么恐怕标准c++没有办法做到这一点。你必须使用其中任何一个

  • 一个元编译器,如Qt元对象编译器,它翻译你的代码,添加额外的元信息。
  • 由宏组成的框架,允许您添加所需的元信息。你需要告诉框架所有的方法、类名、基类和它需要的一切。

c++的设计考虑到了速度。如果您想要高级别的检查,就像c#或Java所做的那样,不做一些额外的工作就无法做到这一点。

我曾经做过与您所追求的类似的事情,虽然可以获得某种级别的反射和访问更高级别的功能,但维护方面的麻烦可能并不值得。我的系统通过委托(类似于Objective-C的消息传递和转发概念)将UI类与业务逻辑完全分离。实现方法是创建一些基类,它能够将符号(我使用了字符串池,但如果您更喜欢速度和编译时错误处理而不是总体灵活性,则可以使用枚举)映射到函数指针(实际上不是纯函数指针,但类似于Boost中的Boost)。函数——我当时没有权限使用它)。你可以为你的成员变量做同样的事情,只要你有一些公共基类能够表示任何值。整个系统是一个不加掩饰的Key-Value编码和委托的剽窃,有一些副作用,可能值得花费大量的时间让每个使用系统的类匹配其所有方法和成员的合法调用:1)任何类都可以调用任何其他类的任何方法,而不必包含头文件或编写假基类,这样就可以为编译器预定义接口;2)成员变量的getter和setter很容易实现线程安全,因为更改或访问它们的值总是通过所有对象基类中的2个方法来完成。

它还导致了做一些非常奇怪的事情的可能性,否则在c++中是不容易的。例如,我可以创建一个Array对象,其中包含任何类型的任意项,包括它本身,并通过向所有数组项传递消息并收集返回值来动态创建新数组(类似于Lisp中的map)。另一个是键值观察的实现,因此我能够设置UI来立即响应后端类成员的变化,而不是不断地轮询数据或不必要的重绘显示。

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

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

至于如何实现这样的东西:只是使用共享和弱指针指向一些公共基础(我的非常有想象力地称为“对象”),并派生所有你想使用的类型。我建议安装Boost。函数,而不是像我那样,用一些自定义的垃圾和大量丑陋的宏来包装函数指针调用。由于所有内容都被映射,因此检查对象只是遍历所有键的问题。由于我的类本质上是尽可能使用c++直接抄袭Cocoa,如果你想要类似的东西,那么我建议使用Cocoa文档作为蓝本。

这些信息确实存在——但不是你需要的格式,而且只有当你导出你的类时。这适用于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输出等等,而且不需要从特定的基类派生。如果有足够的需求,也许我们可以把它做成样子发布。

当我想在c++中反射时,我读了这篇文章,并改进了我在那里看到的东西。对不起,没有罐头。结果不是我的…但你当然可以得到我所拥有的,然后从那里开始。

我目前正在研究,当我喜欢的时候,使用inherit_linear的方法使可反射类型的定义更容易。实际上我已经学了很多,但还有一段路要走。c++ 0x中的变化很可能在这方面有很大的帮助。

编辑: 不再维护;有两个fork可供选择:

  • 一个也被称为,并且基于相同的API。
  • 思考是一个部分重写,应该是首选的,因为它不需要Boost;它使用c++ 11。

是一个MIT许可的库(以前是LGPL),它向c++语言添加了反射。它在编译中不需要特定的预处理步骤,但是必须手动进行绑定。

目前Tegesoft库使用Boost,但也有一把叉子使用c++ 11,即不再需要Boost. 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
)

因此,使用提振。页迭代每个参数并生成如下数据:

// 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;
};
};

现在要遍历字段,我们使用访问者模式。我们创建一个MPL范围,从0到字段的数量,并访问该索引下的字段数据。然后它将字段数据传递给用户提供的访问者:

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

瞧,我们刚刚用c++实现了反射,用了不到100行代码。

c++的开箱即用不支持反射。这很可悲,因为它让防御性测试变得很痛苦。

有几种进行反思的方法:

  1. 使用调试信息(不可移植)。
  2. 在代码中加入宏/模板或其他源代码方法(看起来很难看)
  3. 修改编译器(如clang/gcc)以生成数据库。
  4. 使用Qt moc方法
  5. < a href = " http://bytemaster.github。io/boost_reflect/" rel="noreferrer">Boost Reflect . io/boost_reflect/" rel="noreferrer">
  6. 精确平面反射 .

第一个链接看起来最有希望(使用mod的clang),第二个讨论了一些技术,第三个是使用gcc的不同方法:

  1. < p > http://www.donw.org/rfl/

  2. < p > https://bitbucket.org/dwilliamson/clreflect

  3. < p > https://root.cern.ch/how/how-use-reflex

现在有一个c++反射工作组。查看c++ 14 @ CERN的新闻:

  • < a href = " https://root.cern。ch /博客/ status-reflection-c noreferrer“rel = > https://root.cern.ch/blog/status-reflection-c < / >

编辑13/08/17:

自最初的帖子以来,已经有了一些关于反思的潜在进展。下面提供了关于各种技术和状态的更多细节和讨论:

  1. 果壳中的静态反射
  2. 静态反射
  3. 静态反射设计

然而,在不久的将来,在c++中实现标准化的反射方法看起来并不有希望,除非社区对支持c++中的反射有更多的兴趣。

下面是基于上次c++标准会议反馈的当前状态:

编辑13/12/2017

Reflection看起来正在向c++ 20或更高版本的TSR迈进。然而运动是缓慢的。

编辑15/09/2018

TS草案已送交国家机构进行投票。

文本可以在这里找到:https://github.com/cplusplus/reflection-ts

编辑11/07/2019

反射TS已完成功能,并将于2019年夏天接受评论和投票。

元模板编程方法将被更简单的编译时代码方法所取代(在TS中没有反映出来)。

编辑10/02/2020

这里有一个在Visual Studio中支持反射TS的请求:

作者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

2021年2月24日编辑

一些额外的clang工具方法:

2021年8月25日编辑

在youtube上的一个ACCU在线演讲https://www.youtube.com/watch?v=60ECEc-URP8也很值得一听,它讨论了当前的标准建议和基于clang的实现。

看到的:

尽管在c++中不支持即时可用的反射,但实现它并不太难。 我遇到了这篇很棒的文章: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html < / p >

本文详细介绍了如何实现一个非常简单的反射系统。当然,这不是最有益的解决办法,还有一些粗糙的地方有待解决,但对我的需要来说,这已经足够了。

底线——如果做得正确,反射是可以得到回报的,而且在c++中是完全可行的。

查看Classdesc http://classdesc.sf.net。它以类“描述符”的形式提供了反射,可以与任何标准c++编译器一起工作(是的,它可以与Visual Studio和GCC一起工作),并且不需要源代码注释(尽管存在一些处理棘手情况的pragmas)。它已经开发了十多年,并在一些工业规模的项目中使用。

编辑:更新的坏链接截至2017年2月7日。

我想没有人提到这一点:

在CERN,他们使用c++的全反射系统:

欧洲核子研究中心的反射。看起来效果很好。

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

c++中还有另一个用于反射的新库,名为RTTR(运行时类型反射,也请参阅github)。

该接口类似于c#中的反射,并且不需要任何RTTI。

思考是一个c++反射库,以回答这个问题。我考虑了这些选择,决定自己做一个,因为我找不到一个符合我所有要求的。

虽然这个问题有很好的答案,但我不想使用大量宏,也不想依赖Boost。Boost是一个很棒的库,但也有很多小型定制的c++ 0x项目,它们更简单,编译时间更快。能够从外部装饰一个类也有好处,比如包装一个不支持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());
}

我们在“secret宏”附近有一个泛型函数;)

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宏,你可以构建序列化、比较、散列和其他东西,而不需要触及原始类型,唯一的要求是为每个枚举器(如BinaryWriter)实现每个类型的“EnumerateWith”方法,这是不可枚举的。通常你必须实现10-20个“简单”类型来支持项目中的任何类型。

这个宏在运行时创建/销毁结构的开销应该为零,并且T.EnumerateWith()的代码应该按需生成,这可以通过使其成为模板内联函数来实现,因此所有故事中唯一的开销是向每个结构添加ENUMERATE_MEMBERS(m1,m2,m3…),而在任何解决方案中,每个成员类型实现特定的方法都是必须的,因此我不认为这是开销。

< p >更新: 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);

你可以像这样在内存中为该函数分配一个位置(需要libdldlopen)

#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()libdldlfcn.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 < / p > 示例应用程序也驻留在git存储库中,在这里: https://github.com/tapika/TestCppReflect/ < / p >

我将部分复制在这里并进行解释:

#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使用类名+字段名加上offsetof -来确定特定字段位于内存中的哪个位置。我已经尽可能地学习。net术语,但是c++和c#是不同的,所以不是一对一的。整个c++反射模型驻留在TypeInfoFieldInfo类中。

我已经使用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 < / p >

我试图更深入地解释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

但是因为这个话题很高级,我建议先看视频。

如果您有一些改进的想法,请随时与我联系。

你可以通过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)。

遍历字段可以简单到…

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之上,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 ]
}

参见……