C++反射:全面解读property的实现机制!

2023-04-1912:20:41编程语言入门到精通Comments1,080 views字数 9049阅读模式

本文将深入Property的部分进行介绍,相比较[[reflection function implement]],Property涉及的Tag Dispatch和中间过程更复杂,整体的实现需要一步一步来理清,我们还是从例子入手,从Property的注册和使用来展开整体的实现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

一、 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);

上面的代码分为两部分:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(一)注册的代码

注册的代码通过__register_type<T>()创建的ClassBuilder提供的。property(name,property)函数来完成对属性的注册。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(二)使用的代码

使用的代码,先获取到MetaClass,再从MetaClass中通过TryProperty()通过名字查询到对应的reflection::Property,然后我们可以通过Property的Get(),Set()方法来对对应UserObject对象的属性进行设置和获取。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(三)整体文章的展开思路

我们的讲述会按照下面的顺序逐步展开:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • 一些基础知识。
  • Property运行时的承载对象Property类。
  • 编译期的注册机制。
  • 不同的Property特化实现。
  • 运行时获取值、设置值的具体过程。

二、基础知识

C++中的Property多以Member Object的方式表达,MemberObject的类型和处理方式比较特殊。以framework::math::Vector3举例:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

Vector3的成员变量定义如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

class Vector3 { public:  double x;  double y;  double z;};

以下代码是用来获取成员变量y的:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

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

简单总结如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • 通过T(C::) 或者T C::来表达成员变量的类型,如上例中的 double(Framework::math::Vector3::) 。
  • 通过成员变量取地址的方式获取对应成员的地址,如上例中的&framework::math::Vector3::y。
  • 如上例中,可以通过tmpvec.tmppy这种获取方式来获取对应对象中的成员(获取的是tmpvec.y的值)。
  • 正常如果不是实现反射,很少使用相关的特性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

三、运行时属性的表达-Property类

为了实现运行时Property,所有的Property需要进行类型擦除,以一致的外观进行组织和调用,framework中的Property实现如下(节选):文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

class Property : public Type { public:  IdReturn name() const;
  ValueKind kind() const;
  virtual bool IsReadable() const;
  virtual bool IsWritable() const;
  Value Get(const UserObject& object) const;
  void Set(const UserObject& object, const Value& value) const;
  inline TypeId type_index() const { return type_index_; }
  inline auto implement_type() const { return implement_type_; } protected:  virtual Value GetValue(const UserObject& object) const = 0;    virtual void SetValue(const UserObject& object, const Value& value) const = 0;};

主要使用的是Get(),Set()两个方法,用于从UserObject中获取和设置指定Property的值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

四、依赖的核心机制

虽然Property整体的机制比较复杂,但核心依赖的机制实现比较简洁,主要依赖的是ValueBinder<>和ValueBinder2<>,以及与这两者基本一致的InternetRefBinder<>和InternetRefBinder2<>模板类。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(一)ValueBinder<>和ValueBinder2<>

ValueBinder<>的实现如下图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

上图中还有个依赖的Binding对象,具体的信息如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

相关的Function和Member的Traits我们下文中会具体展开,本部分主要关注ValueBinder和两个Traits内部的TBinding模板类的实现。先从ValueBinder的具体代码说起:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

template <class C, typename PropTraits>class ValueBinder { public:  using ClassType = C;  using AccessType = typename std::conditional<    PropTraits::kIsWritable,     typename PropTraits::AccessType&,     typename PropTraits::AccessType  >::type;    using SetType = typename std::remove_reference<AccessType>::type;  using Binding = typename PropTraits::template TBinding<ClassType, AccessType>;
  ValueBinder(const Binding& b) : bound_(b) {}  AccessType Getter(ClassType& c) const { return bound_.Access(c); }  bool Setter(ClassType& c, SetType v) const {    if constexpr (PropTraits::kIsWritable)      return this->bound_.Access(c) = v, true;    else      return false;  }  bool Setter(ClassType& c, Value const& value) const { return Setter(c, value.to<SetType>()); }  Value GetValue(ClassType& c) const {    if constexpr (PropTraits::kIsWritable)      return UserObject::MakeRef(Getter(c));    else      return UserObject::MakeCopy(Getter(c));  } protected:  Binding bound_;};

Getter(),Setter()的实现主要依托Traits内的TBinding::Access()来实现,也就是我们上图中所贴出的TFunctionTraits<T>::TBinding和 TMemberTraits<T>::TBinding实现,这样在模板层面,我们就有了一个获取和设置对象属性的模板类了,当然,真正将ValueBinder用起来,我们还需要其他的模板设施,暂时我们先关注最核心的这部分。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

除了ValueBinder外,反射库也提供了ValueBinder2模板类,看实现可以发现,主要是提供了外部额外提供一个函数来做Setter的机制:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

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类型的对象服务的,此处不详细赘述了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(三)反射框架中类名后的数字

Propety部分相关的模板类,不少都有数字,如ValueBinder2<>,InternalRefBinder2<>,GetSet2<>等,都是两个参数版本的property注册使用的,一个参数指定getter,一个参数指定setter,setter。前面介绍ValueBinder2的时候也有说到,ValueBinder2通过额外的function对象重载了Setter()接口。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

五、属性的注册

ClassBuilder提供了两个版本的property注册函数,第一个版本对应的是一个accessor的版本:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

template <typename T>template <typename F>ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F accessor) {  if (target_->properties_table_.find(name.data()) == target_->properties_table_.end()) {    return AddProperty(detail::PropertyFactory1<T, F>::Create(name, accessor));  } else {    current_type_ = const_cast<Property*>(&(target_->GetProperty(name)));    return *this;  }}

第二个版本对应的是两个accessor的版本:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

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(),下文中会具体展开相关的实现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

六、PropertyFactory1<T, F>

&PropertyFactory2<T, F1, F2>具体实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

先以PropertyFactory1<T,F>::Create的处理过程为例,来看一下整体Property的创建流程:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

整体处理流程如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • 根据C,T推导正确的Accessor和PropertyImpl,主要是利用GetSet1<>模板类。
  • 利用GetSet1<>模板类中定义的Access类型, 关联正确的AccessTraits类型。
  • 利用AccessTraits类型中定义的Impl和ValueBinder类型正确的产生InterfaceType和Property的具体Impl类(如SimplePropertyImpl类)。

整个处理过程比较复杂,下文中将详细展开相关的类。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

PropertyFactory2<>的处理流程基本与PropertyFactory1<>的处理流程一致, 主要的区别在于PropertyFactory2创建的Property的Setter是通过F2来指定的, 不详细细述了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(一)PropertyFactor与GetSet<>模板类

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

通过上图的关系, 我们也能很容易的看到PropertyFactory处理属性的类别,主要是三类:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • GetSet1<TFunctionTraits<T>>用来处理以单个Getter函数提供的属性。
  • GetSet1<TMemberTaits<T>>用来处理以第3节中介绍的,直接用Member Object来表达的属性。
  • GetSet2<>用于表达以两个函数分别表达getter,setter的属性。

这里涉及的TFunctionTraits,TMemberTratis的定义如下所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • TFunctionTraits

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

中间利用了另外一个辅助的模板类TCallableDetails<T>,细节如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

整个Function Traits主要是对各种不同函数类型的特化表达,最后方便我们获取:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • ParamTypes: 参数类型列表。
  • ReturnType: 返回值类型。
  • FuncType: 函数类型。
  • DispatchType: 用于构建std::function<>的模板参数。
  • FunctionCallTypes: 同ParamTypes。
  • TMemberTraits

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(二)GetSet模板类的实现

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

GetSet1模板类的定义与GetSet2基本一致,除了GetSet2明确利用函数来表达getter,setter。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(三)AccessTratis<>模板类的实现

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

如上图所示,AccessTraits的核心信息比较少,主要是以下几项:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • kind: 属性的类别,主要是两类,MemberObject和Function。
  • using ValueBinder: GetSet1用到的属性绑定类型。
  • using ValueBinder2: GetSet2用到的属性绑定类型。

AccessTraits主要有以下几类:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

覆盖了我们反射支持的所有属性类型:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • SimplePropertyImpl
  • EnumPropertyImpl
  • ArrayPropertyImpl
  • UserPropertyImpl

七、不同的Property特化实现

要实现运行时Property特性,光有上述介绍的GetSet<>,AccesssTraits<>模板类是不够的,我们需要通过具体的PropertyImpl来将相关的功能串联起来。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(一)SimplePropertyImpl

template <typename A>class SimplePropertyImpl : public SimpleProperty { public:  SimplePropertyImpl(IdRef name, A accessor); protected:  bool IsReadable() const final;  bool IsWritable() const final;  Value GetValue(const UserObject& object) const final {      return Value{accessor_.interface_.Getter(object.get<typename A::ClassType>())};  }  void SetValue(const UserObject& object, const Value& value) const final {      if (!accessor_.interface_.Setter(object.Ref<typename A::ClassType>(), value.to<typename A::DataType>()))          PONDER_ERROR(ForbiddenWrite(name()));  } private:  A accessor_;  // Accessor used to access the actual C++ property};

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

如图所示,以 SimplePropertyImpl<>为桥梁,将GetSet1<>,ValueBinder<>等模板类串联到一起,完成了对一个具体的UserObject某个属性进行设置和获取的功能实现(中间还有GetSet模板与AccessTraits模板的串接,上文中已经交代,这里不再重复。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

另外的几个PropertyImpl,如EnumPropertyImpl,ArrayPropertyImpl,UserPropertyImpl与SimplePropertyImpl的实现大同小异,这里不一一展开了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

八、获取值、设置值的具体过程

我们以最前面例子中获取属性值时的调用栈以实际运行的例子来看一下整个运行时获取属性值的过程:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

调用栈不太方便分析, 我们适当格式化方便分析, 我们从上图中从外到内的顺序来具体看一下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(一)Stack Level1

格式化后的调用栈:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

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

对应的代码截图:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(二)Stack Level2

格式化后的调用栈:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

//code 2:framework::reflection::detail::ValueBinder<    framework::math::Vector3,    framework::reflection::detail::TMemberTraits<        double framework::math::Vector3::*    >>::Getter(framework::math::Vector3 & c);

对应的代码截图:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(三)Stack Level3

格式化后的调用栈:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

//code 3:framework::reflection::detail::TMemberTraits<    double framework::math::Vector3::*>::TBinding<framework::math::Vector3,double &>::Access(framework::math::Vector3 & c)

对应的代码截图:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

(四)小结

利用多个模板类的级联和使用,我们最后通过SimplePropertyImpl<>完成了运行时动态获取属性的目的,设置的过程与获取的过程基本一致,这里不重复展开了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

九、总结

通过多层模板的级联,我们完成了运行时动态获取设置属性的功能,另外因为整体代码多利用模板,通过最后一节的分析,我们也能发现,整体的性能其实是比较高的,更多还是依赖模板自身的特性和Tag Dispatch来完成了相关的功能。同时,也能发现,如果仅依托c++17的特性,模板之间的关联会比较弱,整体代码的维护和理解会比较麻烦。后续我们考虑用c++20的concept重构整个反射库,到时再额外输出相关的文章了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

 作者简介文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

C++反射:全面解读property的实现机制!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

沈芳文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

腾讯后台开发工程师文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

IEG研发效能部开发人员,毕业于华中科技大学。目前负责CrossEngine Server的开发工作,对GamePlay技术比较感兴趣。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/36077.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/36077.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定