在上篇《C++反射:深入浅出剖析ponder库实现机制!》中我们对反射实现的整体做了相关的介绍,本篇将深入Property的部分进行介绍。 一、 Property示例代码 //------------------------------------- //register code //------------------------------------- __register_type<Vector3>('Vector3') .constructor() .constructor<double, double, double>() .property('x', &Vector3::x) .property('y', &Vector3::y) .property('z', &Vector3::z) );
//------------------------------------- //use code //------------------------------------- auto* metaClass = __type_of<framework::math::Vector3>(); ASSERT_TRUE(metaClass != nullptr);
auto obj = runtime::CreateWithArgs(*metaClass, Args{ 1.0, 2.0, 3.0 }); ASSERT_TRUE(obj != UserObject::nothing);
const reflection::Property* fieldX = nullptr; metaClass->TryProperty('x', fieldX); ASSERT_TRUE(fieldX != nullptr); double x = fieldX->Get(obj).To<double>(); ASSERT_DOUBLE_EQ(1.0, x); fieldX->Set(obj, 2.0); x = fieldX->Get(obj).to<double>(); ASSERT_DOUBLE_EQ(2.0, x); 上面的代码分为两部分: (一)注册的代码注册的代码通过__register_type<T>()创建的ClassBuilder提供的。property(name,property)函数来完成对属性的注册。(二)使用的代码使用的代码,先获取到MetaClass,再从MetaClass中通过TryProperty()通过名字查询到对应的reflection::Property,然后我们可以通过Property的Get(),Set()方法来对对应UserObject对象的属性进行设置和获取。(三)整体文章的展开思路 我们的讲述会按照下面的顺序逐步展开:
二、基础知识 C++中的Property多以Member Object的方式表达,MemberObject的类型和处理方式比较特殊。以framework::math::Vector3举例: Vector3的成员变量定义如下:
以下代码是用来获取成员变量y的: //using MemberType = double(framework::math::Vector3::*); using MemberType = double framework::math::Vector3::*; MemberType tmppy = &framework::math::Vector3::y; framework::math::Vector3 tmpvec(1.0, 2.0, 3.0); auto tmpy = tmpvec.*tmppy; 简单总结如下:
三、运行时属性的表达-Property类 为了实现运行时Property,所有的Property需要进行类型擦除,以一致的外观进行组织和调用,framework中的Property实现如下(节选):
主要使用的是Get(),Set()两个方法,用于从UserObject中获取和设置指定Property的值。 四、依赖的核心机制 虽然Property整体的机制比较复杂,但核心依赖的机制实现比较简洁,主要依赖的是ValueBinder<>和ValueBinder2<>,以及与这两者基本一致的InternetRefBinder<>和InternetRefBinder2<>模板类。 (一)ValueBinder<>和ValueBinder2<>ValueBinder<>的实现如下图所示: 上图中还有个依赖的Binding对象,具体的信息如下: 相关的Function和Member的Traits我们下文中会具体展开,本部分主要关注ValueBinder和两个Traits内部的TBinding模板类的实现。先从ValueBinder的具体代码说起:
Getter(),Setter()的实现主要依托Traits内的TBinding::Access()来实现,也就是我们上图中所贴出的TFunctionTraits<T>::TBinding和 TMemberTraits<T>::TBinding实现,这样在模板层面,我们就有了一个获取和设置对象属性的模板类了,当然,真正将ValueBinder用起来,我们还需要其他的模板设施,暂时我们先关注最核心的这部分。 除了ValueBinder外,反射库也提供了ValueBinder2模板类,看实现可以发现,主要是提供了外部额外提供一个函数来做Setter的机制: template <class C, typename PropTraits> class ValueBinder2 : public ValueBinder<C, PropTraits> { using Base = ValueBinder<C, PropTraits>; public: template <typename S> ValueBinder2(const typename Base::Binding& g, S s) : Base(g), set_(s) {} bool Setter(typename Base::ClassType& c, typename Base::SetType v) const { return set_(c, v), true; } bool Setter(typename Base::ClassType& c, Value const& value) const { return Setter(c, value.to<typename Base::SetType>()); } protected: std::function<void(typename Base::ClassType&, typename Base::AccessType)> set_; }; (二)Internet RefBinder<>与Internet RefBinder2<>实现与ValueBinder提供的接口完全一致,主要是为UserObject类型的对象服务的,此处不详细赘述了。 (三)反射框架中类名后的数字Propety部分相关的模板类,不少都有数字,如ValueBinder2<>,InternalRefBinder2<>,GetSet2<>等,都是两个参数版本的property注册使用的,一个参数指定getter,一个参数指定setter,setter。前面介绍ValueBinder2的时候也有说到,ValueBinder2通过额外的function对象重载了Setter()接口。 五、属性的注册 ClassBuilder提供了两个版本的property注册函数,第一个版本对应的是一个accessor的版本:
第二个版本对应的是两个accessor的版本: template <typename T> template <typename F1, typename F2> ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F1 accessor1, F2 accessor2) { if (target_->properties_table_.find(name.data()) == target_->properties_table_.end()) { return AddProperty(detail::PropertyFactory2<T, F1, F2>::Create(name, accessor1, accessor2)); } else { current_type_ = const_cast<Property*>(&(target_->GetProperty(name))); return *this; } } 从上述的两段代码可以看到,直接负责创建Property的是模板类的Create函数,PropertyFactory1<T,F>::Create()和PropertyFactory2<T, F1,F2>::Create(),下文中会具体展开相关的实现。 六、PropertyFactory1<T, F> &PropertyFactory2<T, F1, F2>具体实现 先以PropertyFactory1<T,F>::Create的处理过程为例,来看一下整体Property的创建流程: 整体处理流程如下:
整个处理过程比较复杂,下文中将详细展开相关的类。 PropertyFactory2<>的处理流程基本与PropertyFactory1<>的处理流程一致, 主要的区别在于PropertyFactory2创建的Property的Setter是通过F2来指定的, 不详细细述了。 (一)PropertyFactor与GetSet<>模板类通过上图的关系, 我们也能很容易的看到PropertyFactory处理属性的类别,主要是三类:
这里涉及的TFunctionTraits,TMemberTratis的定义如下所示:
中间利用了另外一个辅助的模板类TCallableDetails<T>,细节如下: 整个Function Traits主要是对各种不同函数类型的特化表达,最后方便我们获取:
(二)GetSet模板类的实现GetSet1模板类的定义与GetSet2基本一致,除了GetSet2明确利用函数来表达getter,setter。 (三)AccessTratis<>模板类的实现如上图所示,AccessTraits的核心信息比较少,主要是以下几项:
AccessTraits主要有以下几类: 覆盖了我们反射支持的所有属性类型:
七、不同的Property特化实现 要实现运行时Property特性,光有上述介绍的GetSet<>,AccesssTraits<>模板类是不够的,我们需要通过具体的PropertyImpl来将相关的功能串联起来。 (一)SimplePropertyImpl
如图所示,以 SimplePropertyImpl<>为桥梁,将GetSet1<>,ValueBinder<>等模板类串联到一起,完成了对一个具体的UserObject某个属性进行设置和获取的功能实现(中间还有GetSet模板与AccessTraits模板的串接,上文中已经交代,这里不再重复。 另外的几个PropertyImpl,如EnumPropertyImpl,ArrayPropertyImpl,UserPropertyImpl与SimplePropertyImpl的实现大同小异,这里不一一展开了。 八、获取值、设置值的具体过程 我们以最前面例子中获取属性值时的调用栈以实际运行的例子来看一下整个运行时获取属性值的过程: 调用栈不太方便分析, 我们适当格式化方便分析, 我们从上图中从外到内的顺序来具体看一下: (一)Stack Level1格式化后的调用栈: //code 1: framework::reflection::detail::SimplePropertyImpl< framework::reflection::detail::GetSet1< framework::math::Vector3, framework::reflection::detail::TMemberTraits<double framework::math::Vector3::*> > >::GetValue(const framework::reflection::UserObject & object); 对应的代码截图: (二)Stack Level2格式化后的调用栈:
对应的代码截图: (三)Stack Level3格式化后的调用栈: //code 3: framework::reflection::detail::TMemberTraits< double framework::math::Vector3::* >::TBinding<framework::math::Vector3,double &>::Access(framework::math::Vector3 & c) 对应的代码截图: (四)小结利用多个模板类的级联和使用,我们最后通过SimplePropertyImpl<>完成了运行时动态获取属性的目的,设置的过程与获取的过程基本一致,这里不重复展开了。 九、总结 通过多层模板的级联,我们完成了运行时动态获取设置属性的功能,另外因为整体代码多利用模板,通过最后一节的分析,我们也能发现,整体的性能其实是比较高的,更多还是依赖模板自身的特性和Tag Dispatch来完成了相关的功能。同时,也能发现,如果仅依托c++17的特性,模板之间的关联会比较弱,整体代码的维护和理解会比较麻烦。后续我们考虑用c++20的concept重构整个反射库,到时再额外输出相关的文章了。 - EOF - |
|
来自: 520jefferson > 《c/c 》