在实际的项目开发中,我们有时候需要进行一些操作比如说序列化。而如果我们对所有的内容都进行hardcode,显然这样的工作比较繁琐。因此,使用反射可以让我们在运行时获得类型信息,从而更方便的进行开发。在C++中原生不能提供反射机制,因此,通常需要我们手动去实现。
在C++中,常见的反射实现通常分为动态反射和静态反射。静态反射将类型信息在编译时进行导出,通常情况下使用parser在编译时期生成一些辅助代码,通过对类型、字段和函数的标记,生成相应的反射类型信息,如Unreal
Engine、Qt等都是通过静态反射去实现反射的功能。动态反射是在运行时将类型信息记录下来,通常需要我们手动去register所需要的反射信息。
一种简单的反射实现
首先,我们可以从简单开始,考虑一个最基本的问题,我们如何通过一个类名的字符串创建出对应的类。我们可以通过一个Class工厂类,存储我们注册的类名和类的构造信息。在创建类的同时,我们实现一个类的回调构造函数,这就是我们在工厂类中保存的类的构造信息,这样我们就可以使用类名字符串创建对应的类了。代码如下
(源自 我所理解的
C++反射机制 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <map> #include <iostream> #include <string> using namespace std;typedef void * (*PTRCreateObject)(void ); class ClassFactory {private : map<string, PTRCreateObject> m_classMap ; ClassFactory (){}; public : void * getClassByName (string className) ; void registClass (string name, PTRCreateObject method) ; static ClassFactory& getInstance () ; }; void * ClassFactory::getClassByName (string className) { map<string, PTRCreateObject>::const_iterator iter; iter = m_classMap.find (className) ; if ( iter == m_classMap.end () ) return NULL ; else return iter->second () ; } void ClassFactory::registClass (string name, PTRCreateObject method) { m_classMap.insert (pair <string, PTRCreateObject>(name, method)) ; } ClassFactory& ClassFactory::getInstance () { static ClassFactory sLo_factory; return sLo_factory ; } class RegisterAction {public : RegisterAction (string className,PTRCreateObject ptrCreateFn){ ClassFactory::getInstance ().registClass (className,ptrCreateFn); } }; #define REGISTER(className) \ className* objectCreator##className(){ \ return new className; \ } \ RegisterAction g_creatorRegister##className( \ #className,(PTRCreateObject)objectCreator##className) class TestClass {public : void m_print () { cout<<"hello TestClass" <<endl; }; }; REGISTER (TestClass);int main (int argc,char * argv[]) { TestClass* ptrObj=(TestClass*)ClassFactory::getInstance ().getClassByName ("TestClass" ); ptrObj->m_print (); }
Taichi 反射库
Taichi Training类似于RTTR实现了一套动态反射库作为教程,教程链接40
分钟搓一个 C++
反射库【原理、用法、实现都在这里了!】_哔哩哔哩_bilibili
首先,我们可以先看一段测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Foo { public : void PassByValue (std::string s) const { std::cout << "Foo::PassByValue(`" << s << "`)" << std::endl; } void PassByConstRef (const std::string &s) const { std::cout << "Foo::PassByConstRef(const `" << s << "` &)" << std::endl; } std::string Concat (const std::string &head, const std::string &tail) { auto res = head + tail; return res; } std::shared_ptr<float > MakeFloatPtr (float i) { return std::make_shared <float >(i); } static void MakeReflectable () { reflect::AddClass <Foo>("Foo" ) .AddMemberVar ("name" , &Foo::name) .AddMemberVar ("x_" , &Foo::x_) .AddMemberFunc ("PassByValue" , &Foo::PassByValue) .AddMemberFunc ("PassByConstRef" , &Foo::PassByConstRef) .AddMemberFunc ("Concat" , &Foo::Concat) .AddMemberFunc ("MakeFloatPtr" , &Foo::MakeFloatPtr); } int x () const { return x_; } std::string name; private : int x_{0 }; };
注册类型信息的代码就在MakeReflectable
中,可以看到这里采用了一个建造者模式,可以链式的对成员函数和成员变量进行register。首先,在AddClass
中会创建出这样的一个Builder
1 2 3 4 5 template <typename T>details::TypeDescriptorBuilder<T> AddClass (const std::string &name) { details::TypeDescriptorBuilder<T> b{name}; return b; }
这样的Builder最终想要创建的就是一个TypeDescriptor
,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class TypeDescriptor { public : const std::string &name () const { return name_; } const std::vector<MemberVariable> &member_vars () const { return member_vars_; } const std::vector<MemberFunction> &member_funcs () const { return member_funcs_; } MemberVariable GetMemberVar (const std::string &name) const { for (const auto &mv : member_vars_) { if (mv.name () == name) { return mv; } } return MemberVariable{}; } MemberFunction GetMemberFunc (const std::string &name) const { for (const auto &mf : member_funcs_) { if (mf.name () == name) { return mf; } } return MemberFunction{}; } private : friend class RawTypeDescriptorBuilder ; std::string name_; std::vector<MemberVariable> member_vars_; std::vector<MemberFunction> member_funcs_; };
成员变量
在MemberVariable
的实现中,使用了C++中的一种Type
Eraser的方法,这样在编译时我们不需要类型信息,将类型信息的处理放在了运行时进行,也就是将类型的处理交给了使用者。也就是说这里MemberVariable本身不包含任何模板,类型的传递在构造函数以及getter和setter中使用模板进行推导。getter_
和setter_
的参数和返回值都使用std::any
,也就是任意类型,在更古老的版本中可以用void*
。在getter_
和setter_
的具体实现中,使用std::any_cast
对类型进行强制类型转化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class MemberVariable { public : MemberVariable () = default ; template <typename C, typename T> MemberVariable (T C::*var) { getter_ = [var](std::any obj) -> std::any { return std::any_cast <const C *>(obj)->*var; }; setter_ = [var](std::any obj, std::any val) { auto *self = std::any_cast <C *>(obj); self->*var = std::any_cast <T>(val); }; } const std::string &name () const { return name_; } template <typename T, typename C> T GetValue (const C &c) const { return std::any_cast <T>(getter_ (&c)); } template <typename C, typename T> void SetValue (C &c, T val) { setter_ (&c, val); } private : friend class RawTypeDescriptorBuilder ; std::string name_; std::function<std::any(std::any)> getter_{nullptr }; std::function<void (std::any, std::any)> setter_{nullptr }; };
成员函数
成员函数中,类似的,也采用了Type
Eraser的策略。这里的构造函数从一个变成了四个,分别对应 -
void func(params...)
-
return_type func(params...)
-
void func(params...) const
-
return_type func(params...) const
四种情况。具体的实现中,C::*func
中C
时类的类型,func
时成员函数,Args...
时参数列表。绑定函数到fn_
上调用时,首先会将类C
和参数列表Args...
转化成一个tuple
的指针,然后调用std::apply
函数执行函数返回结果。
另外,在调用的时候,使用std::reference_wrapper
对类型进行了引用封装,用于确保该对象是作为一个引用保存在tuple
中而不是值。
std::make_tuple
Creates a tuple object, deducing the target type from the types of
arguments. For each Ti
in Types...
, the
corresponding type Vi
in VTypes...
is std::decay <Ti>::type unless
application of std::decay results in std::reference_wrapper <X> for
some type X
, in which case the deduced type
is X&
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class MemberFunction { public : MemberFunction () = default ; template <typename C, typename R, typename ... Args> explicit MemberFunction (R (C::*func)(Args...)) { fn_ = [this , func](std::any obj_args) -> std::any { using tuple_t = std::tuple<C &, Args...>; auto *tp_ptr = std::any_cast <tuple_t *>(obj_args); return std::apply (func, *tp_ptr); }; } template <typename C, typename ... Args> explicit MemberFunction (void (C::*func)(Args...)) { fn_ = [this , func](std::any obj_args) -> std::any { using tuple_t = std::tuple<C &, Args...>; auto *tp_ptr = std::any_cast <tuple_t *>(obj_args); std::apply (func, *tp_ptr); return std::any{}; }; } template <typename C, typename R, typename ... Args> explicit MemberFunction (R (C::*func)(Args...) const ) { fn_ = [this , func](std::any obj_args) -> std::any { using tuple_t = std::tuple<const C &, Args...>; auto *tp_ptr = std::any_cast <tuple_t *>(obj_args); return std::apply (func, *tp_ptr); }; is_const_ = true ; } template <typename C, typename ... Args> explicit MemberFunction (void (C::*func)(Args...) const ) { fn_ = [this , func](std::any obj_args) -> std::any { using tuple_t = std::tuple<const C &, Args...>; auto *tp_ptr = std::any_cast <tuple_t *>(obj_args); std::apply (func, *tp_ptr); return std::any{}; }; is_const_ = true ; } const std::string &name () const { return name_; } bool is_const () const { return is_const_; } template <typename C, typename ... Args> std::any Invoke (C &c, Args &&... args) { if (is_const_) { auto tp = std::make_tuple (std::reference_wrapper <const C>(c), args...); return fn_ (&tp); } auto tp = std::make_tuple (std::reference_wrapper <C>(c), args...); return fn_ (&tp); } private : friend class RawTypeDescriptorBuilder ; std::string name_; bool is_const_{false }; std::function<std::any(std::any)> fn_{nullptr }; };
最终的测试和使用
测试代码如下,这里需要注意一点,对于const
reference和reference的参数传递,需要手动添加一个std::ref
和std::cref
的包装,否则会crash。这是因为否则存在tuple中的类型不是一个引用类型,这和我们希望的是冲突的,所以会产生问题。
这里还有另一个解决办法,作者通过一层中间层ArgWrap
对形参到实参的转化作了处理,细节参考代码和视频
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void TestFoo () { std::cout << ">>> TestFoo\n" << std::endl; Foo::MakeReflectable (); auto foo_t = reflect::GetByName ("Foo" ); for (const auto &mv : foo_t .member_vars ()) { std::cout << "member var: " << mv.name () << std::endl; } std::cout << std::endl; for (const auto &mf : foo_t .member_funcs ()) { std::cout << "member func: " << mf.name () << ", is_const=" << mf.is_const () << std::endl; } std::cout << std::endl; Foo f; auto name_var = foo_t .GetMemberVar ("name" ); name_var.SetValue (f, std::string{"taichi" }); std::cout << "f.name=" << f.name << std::endl; auto x_var = foo_t .GetMemberVar ("x_" ); x_var.SetValue (f, 42 ); std::cout << "f.x=" << f.x () << std::endl; std::cout << std::endl; auto foo_make_float_ptr = foo_t .GetMemberFunc ("MakeFloatPtr" ); auto res = foo_make_float_ptr.Invoke (f, 123.4f ); auto float_sptr = std::any_cast<std::shared_ptr<float >>(res); std::cout << "MakeFloatPtr res: " << *float_sptr << std::endl; std::string hello_s{"hello" }; std::string world_s{" world" }; auto foo_pass_by_val = foo_t .GetMemberFunc ("PassByValue" ); foo_pass_by_val.Invoke (f, hello_s); auto foo_pass_by_cref = foo_t .GetMemberFunc ("PassByConstRef" ); foo_pass_by_cref.Invoke (f, std::cref (hello_s)); auto foo_concat = foo_t .GetMemberFunc ("Concat" ); res = foo_concat.Invoke (f, std::cref (hello_s), std::cref (world_s)); std::cout << "Concat got: " << std::any_cast <std::string>(res) << std::endl; std::cout << std::endl; std::cout << "<<< TestFoo OK\n" << std::endl; }
参考资料