我怎样才能将reflection添加到C ++应用程序?
我想能够反省一个C ++类的名称,内容(即成员和他们的types)等我在这里说本地C ++,而不是托pipeC ++,它有反思。 我意识到C ++使用RTTI提供一些有限的信息。 哪些额外的库(或其他技术)可以提供这些信息?
你需要做的是预处理器生成有关字段的reflection数据。 这些数据可以存储为嵌套类。
首先,为了使它在预处理器中更容易和更清晰,我们将使用types化expression式。 一个types化的expression式只是一个将该types放在括号中的expression式。 所以,而不是写int x
你会写(int) x
。 这里有一些方便的macros来帮助键入expression式:
#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
macros来生成关于每个字段的数据(加上字段本身)。 这个macros将被这样调用:
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
,它是类中可reflection字段的数量。 然后专门为每个领域的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; }; };
现在遍历我们使用访问者模式的字段。 我们创build一个从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
函数,使用reflection数据遍历字段:
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()); }
使用具有可reflectionPerson
类的print_fields
的示例:
int main() { Person p("Tom", 82); print_fields(p); return 0; }
哪些产出:
name=Tom age=82
瞧,我们刚刚在C ++中用100行代码实现了reflection。
游泳有两种reflection
。
- 通过遍历一个types的成员来检查,枚举它的方法等等。
这对于C ++来说是不可能的。
- 通过检查types(类,结构,联合)是否具有方法或嵌套types来检查是否从另一个特定types派生。
C ++使用
template-tricks
。 使用boost::type_traits
很多事情(比如检查一个types是否是整数)。 为了检查成员函数的存在,使用是否可以编写一个模板来检查函数的存在? 。 为了检查某个嵌套types是否存在,使用普通的SFINAE 。
如果你正在寻找方法来完成1),比如查看一个类有多less个方法,或者像获取一个类id的string表示那么恐怕没有标准C ++的方法来做到这一点。 你必须使用任一
- 像Meta编译器一样的Qt元对象编译器,可以将代码转换为额外的元信息。
- 一个框架macros的macros,允许你添加所需的元信息。 你需要告诉框架所有的方法,类名,基类和它需要的一切。
C ++的编译速度很快。 如果你想要像C#或Java那样的高级检查,恐怕我不得不告诉你,没有任何的努力。
我会喜欢一匹小马,但是小马不是免费的。 😛
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI就是你将要得到的。; 像你正在考虑的reflection – 运行时可用的完全描述性元数据 – 默认情况下,C ++不存在。
信息确实存在 – 但不是以您需要的格式,并且仅在您导出类时才适用。 这在Windows中工作,我不知道其他平台。 像使用存储类说明符一样,例如:
class __declspec(export) MyClass { public: void Foo(float x); }
这使编译器将类定义数据构build到DLL / Exe中。 但是,这不是一种可以随时用于反思的格式。
在我的公司,我们build立了一个解释这个元数据的库,并允许你reflection一个类,而不需要在类中插入额外的macros等。 它允许函数被调用如下:
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中的stream式处理等都是非常有用的,并且不需要从特定的基类派生。 如果有足够的需求,也许我们可以把它形成发布。
C ++不存在RTTI。
这是完全错误的。 实际上,“RTTI”这个术语是由C ++标准创造的。 另一方面,RTTI在实施反思方面并没有走的太远。
你需要看看你正在做什么,如果RTTI将满足你的要求。 为了一些特定的目的,我实现了自己的伪reflection。 例如,我曾经希望能够灵活地configuration模拟的输出。 它需要向要输出的类添加一些样板代码:
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()
方法来确定可用的方法。
然后,在configuration文件中,你可以做这样的事情:
FILTER-OUTPUT-OBJECT MyObject FILTER-OUTPUT-FILENAME file.txt FILTER-CLAUSE-1 person == 1773 FILTER-CLAUSE-2 time > 2000
通过一些涉及boost
模板魔法,在运行时(在读取configuration文件的时候),这会被翻译成一系列的方法调用,因此效率很高。 除非你真的需要,否则我不会推荐这样做,但是当你这样做的时候,你可以做一些很酷的东西。
你想用reflection来做什么?
您可以使用Boost typestraits和typeof库作为编译时reflection的有限forms。 也就是说,您可以检查和修改传递给模板的types的基本属性。
我会build议使用Qt 。
有一个开源许可证和一个商业许可证。
我做了一些类似于你之后的事情,虽然可以得到一定程度的思考和访问更高级别的function,但维护头痛可能不值得。 我的系统被用来通过委托类似于Objective-C的消息传递和转发的概念来使UI类与业务逻辑完全分开。 做到这一点的方法是创build一些能够映射符号的基类(我使用了一个string池,但是如果你喜欢速度和编译时error handling,而不是整个灵活性,你可以用枚举来实现)函数指针(实际上不是纯函数指针,但类似于Boost.Function – 我在那时没有访问)的东西。 只要你有一些能代表任何值的公共基类,你可以对你的成员variables做同样的事情。 整个系统是一个毫不掩饰的关键值编码和授权的幌子,有一些副作用也许值得花费大量的时间让每个使用该系统的类将其所有方法和成员与法定调用进行匹配:1)任何类都可以调用任何其他类的任何方法,而不必包含头文件或编写假基类,因此可以为编译器预定义接口; 2)成员variables的getter和setter很容易使线程安全,因为改变或访问它们的值总是通过所有对象的基类中的2个方法来完成的。
这也导致了做一些奇怪的事情的可能性,否则在C ++中是不容易的。 例如,我可以创build一个Array对象,其中包含任意types的任意项目(包括它本身),并通过向所有数组项目传递消息并收集返回值(类似于Lisp中的map)dynamic地创build新数组。 另一个是键值观察的实现,由此我能够设置UI来立即响应后端类成员的变化,而不是不断轮询数据或不必要地重绘显示。
也许对你更有意思的是,你也可以转储为类定义的所有方法和成员,并且不会less于stringforms。
这个系统的缺点可能会让你不受打扰:添加所有的消息和键值是非常单调乏味的; 它比没有任何reflection慢; 你会越来越讨厌看到boost::static_pointer_cast
和boost::dynamic_pointer_cast
在你的代码库中激烈的激情; 强types系统的局限性仍然存在,你真的只是把它们隐藏起来,所以它不是那么明显。 在你的string中input也不是一件有趣或容易发现的惊喜。
至于如何实现这样的东西:只是使用共享和弱指针到一些共同的基础(我的是非常想象的称为“对象”),并为所有你想使用的types派生。 我build议安装Boost.Function而不是像我这样做,这是一些自定义的废话和一大堆丑陋的macros来包装函数指针调用。 由于一切都被映射,检查对象只是遍历所有键的问题。 由于我的课程基本上与Cocoa的直接剥夺尽可能接近,所以如果您需要类似的东西,那么我build议使用Cocoa文档作为蓝图。
我从C ++时代所了解到的两种反思类解决scheme是:
1)如果你能够让所有的类从“对象”基类派生出来,使用RTTI,它将为你提供一个引导来build立类似于reflection的行为。 该类可以提供一些方法,如GetMethod,GetBaseClass等。至于这些方法是如何工作的,你将需要手动添加一些macros来装饰你的types,幕后创buildtypes的元数据以提供GetMethods的答案等。
2)另一种select,如果你有权访问编译器对象就是使用DIA SDK 。 如果我没有记错,这可以让你打开pdbs,它应该包含C ++types的元数据。 做你所需要的就足够了。 这个页面显示了如何获得一个类的所有基types。
这两个解决scheme虽然有点难看! 没有什么比C ++更让你欣赏C#的奢侈品了。
祝你好运。
编辑 : 营地不再维护; 两个叉子可用:
- 一个也被称为CAMP ,并基于相同的API。
- 思考是部分重写,应该是首选,因为它不需要提升; 它使用C ++ 11。
CAMP是MIT许可的图书馆(以前的LGPL),它增加了对C ++语言的反思。 它不需要编译中的特定预处理步骤,但必须手动进行绑定。
目前的Tegesoft库使用Boost,但也有一个使用C ++ 11 的分支 , 不再需要Boost 。
在C ++中有另一个用于reflection的新库,名为RTTR (运行时typesreflection,另请参阅github )。
该接口与C#中的reflection类似,并且没有任何RTTI。
编辑:更新断开的链接截至2017年2月7日。
我想没人提到这个:
在CERN他们使用C ++的全reflection系统:
CERNreflection 。 这似乎工作得很好。
这个问题现在有点老了(不知道为什么我今天一直在问老问题),但是我正在考虑引入编译时reflection的BOOST_FUSION_ADAPT_STRUCT 。
当然这是由你来映射到运行时的reflection,并不是太容易,但是在这个方向上是可能的,而不会是相反的:)
我真的认为封装BOOST_FUSION_ADAPT_STRUCT
的macros可以生成获取运行时行为的必要方法。
我想你可能会发现Dominic Filion的文章“在C ++中使用模板进行reflection”。 这是在游戏编程gem5的第1.4节。 不幸的是我没有我的副本,但find它,因为我认为它解释了你所要求的。
reflection本质上是关于编译器决定在运行时代码可以查询的代码中留下的脚印。 C ++因不支付你不使用的东西而出名, 因为大多数人不使用/想reflection,C ++编译器通过不logging任何东西来避免成本。
所以,C ++没有提供reflection,而且像其他答案所指出的那样,将它自己“模拟”为一般规则并不容易。
在“其他技巧”下,如果你没有反思的语言,可以在编译时获得一个可以提取你想要的信息的工具。
我们的DMS Software Reengineering Toolkit是通过显式语言定义进行参数化的通用编译器技术。 它具有C,C ++,Java,COBOL,PHP等语言的定义。
对于C,C ++,Java和COBOL版本,它提供对parsing树和符号表信息的完整访问。 该符号表信息包括您可能需要从“reflection”中获得的数据types。 如果您的目标是枚举一些字段或方法集合,并使用它们,可以使用DMS以任意方式根据您在符号表中find的内容来转换代码。
您可以在这里find另一个库: http : //www.garret.ru/cppreflection/docs/reflect.html它支持2种方式:从debugging信息获取types信息,让程序员提供这些信息。
我也对我的项目感兴趣,发现这个库,我还没有尝试过,但尝试了这个人的其他工具,我喜欢他们如何工作:-)
查看Classdesc http://classdesc.sf.net 。 它以“描述符”类的forms提供reflection,可以与任何标准的C ++编译器(是的,可以使用Visual Studio以及GCC)一起工作,并且不需要源代码注释(虽然有一些编译指令可以处理棘手的情况)。 已经有十多年的发展历史,并在一些工业规模的项目中使用。
思考是一个C ++reflection库,回答这个问题。 我考虑过这些选项,并决定自己做,因为我找不到一个打勾我所有的盒子。
虽然这个问题有很好的答案,但是我不想使用大量的macros,或者依靠Boost。 Boost是一个伟大的库,但是有很多定制的C ++ 0x项目更简单,编译时间更快。 能够从外部装饰一个类也是有好处的,比如包装一个C ++库(它不支持C ++ 11)。 它是CAMP的分支,使用C ++ 11, 不再需要Boost 。
C ++不支持reflection。 这是令人伤心的,因为这使得防御testing成为一种痛苦。
有几种做反思的方法:
- 使用debugging信息(非便携式)。
- 把你的代码用macros的/ templates或者其他一些源代码方式(看上去很难看)
- 修改一个编译器如clang / gcc来产生一个数据库。
- 使用Qt moc方法
- 提升反映
- 精确和平坦的reflection
第一个链接看起来最有前途(使用mod的叮当声),第二个链接讨论了一些技术,第三个是使用gcc的不同方法:
现在有一个C ++reflection工作组。 查看C ++的新闻14 @ CERN:
编辑13/08/17:由于原来的职位,反思已经有一些潜在的进步。 以下提供更多的细节和对各种技术和状态的讨论:
- 简单的静态reflection
- 静态reflection
- 一个静态reflection的devise
然而,在C ++中,在标准化的reflection方法中,除非社区中有更多的人支持C ++reflection,
以下详细介绍了基于上次C ++标准会议反馈的当前状态:
- 关于思考提案的思考
当我想用C ++进行反思的时候,我阅读了这篇文章,并改进了我在那里看到的内容。 对不起,没有可以。 我不拥有这个结果,但是你一定能得到我所拥有的并且从那里去的东西。
我现在正在研究,当我觉得时,使用inherit_linearly方法使reflectiontypes的定义更容易。 实际上我已经相当的了,但是我还有一段路要走。 C ++ 0x中的更改很可能在这方面有很多帮助。
看起来像C ++仍然没有这个function。 而且C ++ 11也推迟了reflection((
search一些macros或自己。 Qt也可以帮助反思(如果可以使用的话)。
尝试看看这个项目http://www.garret.ru/cppreflection/docs/reflect.html被添加到C ++的反思。 它将元数据添加到您可以使用的类。
尽pipec ++不支持reflection,但实现起来并不困难。 我遇到过这么棒的文章: http : //replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html
文章详细解释了如何实现一个非常简单和基本的reflection系统。 并不是最有益的解决scheme,而且还有一些粗糙的问题需要解决,但是为了我的需要,这已经足够了。
底线 – reflection可以得到回报,如果做得对,而且在C ++中是完全可行的。
我想宣传自动内省/reflection工具包“IDK”的存在。 它使用像Qt这样的元编译器,并将元信息直接添加到对象文件中。 据称很容易使用。 没有外部依赖性。 它甚至可以让你自动反映std :: string,然后在脚本中使用它。 请看看IDK
在C ++中的reflection是非常有用的,在那里你需要为每个成员运行一些方法(例如:序列化,哈希,比较)。 我使用非常简单的语法来使用通用解决scheme:
struct S1 { ENUMERATE_MEMBERS(str,i); std::string str; int i; }; struct S2 { ENUMERATE_MEMBERS(s1,i2); S1 s1; int i2; };
其中ENUMERATE_MEMBERS是一个macros,稍后介绍(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()); }
我们在“秘密macros”附近有通用的function;)
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_MEMBERSmacros,可以在不触及原始types的情况下构build序列化,比较,散列和其他东西,唯一的要求是为每个枚举器(如BinaryWriter)不可枚举的每种types实现“EnumerateWith” 。 通常你将不得不实施10-20个“简单”types来支持你的项目中的任何types。
这个macros在运行时应该有零开销的结构创build/销毁,并且应该根据需要生成T.EnumerateWith()的代码,这可以通过使其成为模板内联函数来实现,所有的故事是添加ENUMERATE_MEMBERS(m1,m2,m3 …)到每个结构,而每个成员types实现特定的方法是任何解决scheme必须的,所以我不认为它是开销。
更新:有非常简单的ENUMERATE_MEMBERSmacros的实现(但是它可能有点扩展以支持从可枚举结构inheritance)
#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行代码不需要任何第三方库;)
Root Reflex项目对此有支持。
更新24.2.2017
Previously I have analyzed support for using #define's and like it's recommended in some of web articles – I've hit across defines in visual C++ were not working identically compared to define's used in gcc (for example, on internet this is quite often referred as "MSVC walkaround"). Besides not be able to easily understand what is happening behind define / macro expansion machinery – it's rather difficult to debug each macro expansion.
There are couple of ways to walk around complexities of define expansion, one approach is to turn on "/P" compiler flag (pre-process to file only) – after that you can compare how your define's opened up. (Previously I have also used also actively stringfy operator (#))
I have collected all useful defines from multiple forums, resorted them, and commented out what is happening behind machinery, you can find whole header file in here now:
https://sourceforge.net/p/testcppreflect/code/HEAD/tree/MacroHelpers.h
I thought it was rather trivial to use these macros to enable C++ reflection, but it requires bit more magic to do the reflection.
I've recollected a working sample code, and put it as sourceforge project, can be downloaded here:
https://sourceforge.net/p/testcppreflect/code/HEAD/tree/
Demo code looks like this:
#include "CppReflect.h" using namespace std; class Person { public: REFLECTABLE( Person, (CString) name, (int) age ) }; class People { public: REFLECTABLE( People, (CString) groupName, (vector<Person>) people ) }; void main(void) { People ppl; ppl.groupName = "Group1"; Person p; p.name = L"Roger"; p.age = 37; ppl.people.push_back(p); p.name = L"Alice"; p.age = 27; ppl.people.push_back( p ); p.name = L"Cindy"; p.age = 17; ppl.people.push_back( p ); CStringA xml = ToXML( &ppl ); CStringW errors; People ppl2; FromXml( &ppl2, xml, errors ); CStringA xml2 = ToXML( &ppl2 ); printf( xml2 ); }
REFLECTABLE define uses class name + field name with offsetof – to identify at which place in memory particular field is located. I have tried to pick up .NET terminology for as far as possible, but C++ and C# are different, so it's not 1 to 1. Whole C++ reflection model resides in TypeInfo and FieldInfo classes for timebeing, it's possible to expand support also to method, but I've decided to keep things simple for now.
I have used pugi xml parser to fetch demo code into xml and restore it back from xml.
So output produced by demo code looks like this:
<?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>
It's also possible to enable any 3-rd party class / structure support via TypeTraits class, and partial template specification – to define your own TypeTraitsT
class, in similar manner to CString
or int
– see example code in
https://sourceforge.net/p/testcppreflect/code/HEAD/tree/TypeTraits.h#l65
template <> class TypeTraitsT<CString> : public TypeTraits { public: virtual CStringW ToString( void* pField ) { CString* s = (CString*)pField; return *s; } virtual void FromString( void* pField, const wchar_t* value ) { CString* s = (CString*)pField; *s = value; } }; template <> class TypeTraitsT<int> : public TypeTraits { public: virtual CStringW ToString( void* pField ) { int* p = (int*) pField; return std::to_string(*p).c_str(); } virtual void FromString( void* pField, const wchar_t* value ) { int* p = (int*)pField; *p = _wtoi(value); } };
I guess only downside of my own implementation is use of __if_exists
– which might be Microsoft compiler specific extension. If someone knows how to walk around it, let me know.
lack of built in reflection in C++ is the single reason why modern C++ is not used for web development (and lacks ORM and other frameworks)
You can try http://www.extreme.indiana.edu/reflcpp/
A simple way is to use the dynamic_cast<>()
operator which, when assigned to an wrong type, returns NULL, so you can upcast to a base concrete class in an easy way, checking the value of the pointer, if it is not NULL, the cast was done, and you got the type of the object.
But this is just a simple solution, and it only provides the type of the objects, you cannot ask what methods it has, like in Java. If you need an advanced solution, there are some frameworks to choose from.