在上篇《C++反射:深入淺出剖析ponder庫(kù)實(shí)現(xiàn)機(jī)制!》中我們對(duì)反射實(shí)現(xiàn)的整體做了相關(guān)的介紹,本篇將深入Property的部分進(jìn)行介紹。
一、 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);
上面的代碼分為兩部分:
(三)整體文章的展開思路
我們的講述會(huì)按照下面的順序逐步展開:
一些基礎(chǔ)知識(shí)。
Property運(yùn)行時(shí)的承載對(duì)象Property類。
編譯期的注冊(cè)機(jī)制。
不同的Property特化實(shí)現(xiàn)。
運(yùn)行時(shí)獲取值、設(shè)置值的具體過(guò)程。
二、基礎(chǔ)知識(shí)
C++中的Property多以Member Object的方式表達(dá),MemberObject的類型和處理方式比較特殊。以framework::math::Vector3舉例:
Vector3的成員變量定義如下:
class Vector3 {
public:
double x;
double y;
double z;
};
以下代碼是用來(lái)獲取成員變量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;
簡(jiǎn)單總結(jié)如下:
通過(guò)T(C::) 或者T C::來(lái)表達(dá)成員變量的類型,如上例中的 double(Framework::math::Vector3::) 。
通過(guò)成員變量取地址的方式獲取對(duì)應(yīng)成員的地址,如上例中的&framework::math::Vector3::y。
如上例中,可以通過(guò)tmpvec.tmppy這種獲取方式來(lái)獲取對(duì)應(yīng)對(duì)象中的成員(獲取的是tmpvec.y的值)。
正常如果不是實(shí)現(xiàn)反射,很少使用相關(guān)的特性。
三、運(yùn)行時(shí)屬性的表達(dá)-Property類
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()兩個(gè)方法,用于從UserObject中獲取和設(shè)置指定Property的值。
四、依賴的核心機(jī)制
雖然Property整體的機(jī)制比較復(fù)雜,但核心依賴的機(jī)制實(shí)現(xiàn)比較簡(jiǎn)潔,主要依賴的是ValueBinder<>和ValueBinder2<>,以及與這兩者基本一致的InternetRefBinder<>和InternetRefBinder2<>模板類。
ValueBinder<>的實(shí)現(xiàn)如下圖所示:
上圖中還有個(gè)依賴的Binding對(duì)象,具體的信息如下:
相關(guān)的Function和Member的Traits我們下文中會(huì)具體展開,本部分主要關(guān)注ValueBinder和兩個(gè)Traits內(nèi)部的TBinding模板類的實(shí)現(xiàn)。先從ValueBinder的具體代碼說(shuō)起:
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()的實(shí)現(xiàn)主要依托Traits內(nèi)的TBinding::Access()來(lái)實(shí)現(xiàn),也就是我們上圖中所貼出的TFunctionTraits<T>::TBinding和 TMemberTraits<T>::TBinding實(shí)現(xiàn),這樣在模板層面,我們就有了一個(gè)獲取和設(shè)置對(duì)象屬性的模板類了,當(dāng)然,真正將ValueBinder用起來(lái),我們還需要其他的模板設(shè)施,暫時(shí)我們先關(guān)注最核心的這部分。
除了ValueBinder外,反射庫(kù)也提供了ValueBinder2模板類,看實(shí)現(xiàn)可以發(fā)現(xiàn),主要是提供了外部額外提供一個(gè)函數(shù)來(lái)做Setter的機(jī)制:
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_;
};
與ValueBinder提供的接口完全一致,主要是為UserObject類型的對(duì)象服務(wù)的,此處不詳細(xì)贅述了。
Propety部分相關(guān)的模板類,不少都有數(shù)字,如ValueBinder2<>,InternalRefBinder2<>,GetSet2<>等,都是兩個(gè)參數(shù)版本的property注冊(cè)使用的,一個(gè)參數(shù)指定getter,一個(gè)參數(shù)指定setter,setter。前面介紹ValueBinder2的時(shí)候也有說(shuō)到,ValueBinder2通過(guò)額外的function對(duì)象重載了Setter()接口。
五、屬性的注冊(cè)
ClassBuilder提供了兩個(gè)版本的property注冊(cè)函數(shù),第一個(gè)版本對(duì)應(yīng)的是一個(gè)accessor的版本:
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;
}
}
第二個(gè)版本對(duì)應(yīng)的是兩個(gè)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;
}
}
從上述的兩段代碼可以看到,直接負(fù)責(zé)創(chuàng)建Property的是模板類的Create函數(shù),PropertyFactory1<T,F(xiàn)>::Create()和PropertyFactory2<T, F1,F(xiàn)2>::Create(),下文中會(huì)具體展開相關(guān)的實(shí)現(xiàn)。
六、PropertyFactory1<T, F>
&PropertyFactory2<T, F1, F2>具體實(shí)現(xiàn)
先以PropertyFactory1<T,F(xiàn)>::Create的處理過(guò)程為例,來(lái)看一下整體Property的創(chuàng)建流程:
整體處理流程如下:
根據(jù)C,T推導(dǎo)正確的Accessor和PropertyImpl,主要是利用GetSet1<>模板類。
利用GetSet1<>模板類中定義的Access類型, 關(guān)聯(lián)正確的AccessTraits類型。
利用AccessTraits類型中定義的Impl和ValueBinder類型正確的產(chǎn)生InterfaceType和Property的具體Impl類(如SimplePropertyImpl類)。
整個(gè)處理過(guò)程比較復(fù)雜,下文中將詳細(xì)展開相關(guān)的類。
PropertyFactory2<>的處理流程基本與PropertyFactory1<>的處理流程一致, 主要的區(qū)別在于PropertyFactory2創(chuàng)建的Property的Setter是通過(guò)F2來(lái)指定的, 不詳細(xì)細(xì)述了。
通過(guò)上圖的關(guān)系, 我們也能很容易的看到PropertyFactory處理屬性的類別,主要是三類:
GetSet1<TFunctionTraits<T>>用來(lái)處理以單個(gè)Getter函數(shù)提供的屬性。
GetSet1<TMemberTaits<T>>用來(lái)處理以第3節(jié)中介紹的,直接用Member Object來(lái)表達(dá)的屬性。
GetSet2<>用于表達(dá)以兩個(gè)函數(shù)分別表達(dá)getter,setter的屬性。
這里涉及的TFunctionTraits,TMemberTratis的定義如下所示:
中間利用了另外一個(gè)輔助的模板類TCallableDetails<T>,細(xì)節(jié)如下:
整個(gè)Function Traits主要是對(duì)各種不同函數(shù)類型的特化表達(dá),最后方便我們獲?。?/span>
ParamTypes: 參數(shù)類型列表。
ReturnType: 返回值類型。
FuncType: 函數(shù)類型。
DispatchType: 用于構(gòu)建std::function<>的模板參數(shù)。
FunctionCallTypes: 同ParamTypes。
GetSet1模板類的定義與GetSet2基本一致,除了GetSet2明確利用函數(shù)來(lái)表達(dá)getter,setter。
如上圖所示,AccessTraits的核心信息比較少,主要是以下幾項(xiàng):
kind: 屬性的類別,主要是兩類,MemberObject和Function。
using ValueBinder: GetSet1用到的屬性綁定類型。
using ValueBinder2: GetSet2用到的屬性綁定類型。
AccessTraits主要有以下幾類:
覆蓋了我們反射支持的所有屬性類型:
SimplePropertyImpl
EnumPropertyImpl
ArrayPropertyImpl
UserPropertyImpl
七、不同的Property特化實(shí)現(xiàn)
要實(shí)現(xiàn)運(yùn)行時(shí)Property特性,光有上述介紹的GetSet<>,AccesssTraits<>模板類是不夠的,我們需要通過(guò)具體的PropertyImpl來(lái)將相關(guān)的功能串聯(lián)起來(lái)。
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
};
如圖所示,以 SimplePropertyImpl<>為橋梁,將GetSet1<>,ValueBinder<>等模板類串聯(lián)到一起,完成了對(duì)一個(gè)具體的UserObject某個(gè)屬性進(jìn)行設(shè)置和獲取的功能實(shí)現(xiàn)(中間還有GetSet模板與AccessTraits模板的串接,上文中已經(jīng)交代,這里不再重復(fù)。
另外的幾個(gè)PropertyImpl,如EnumPropertyImpl,ArrayPropertyImpl,UserPropertyImpl與SimplePropertyImpl的實(shí)現(xiàn)大同小異,這里不一一展開了。
八、獲取值、設(shè)置值的具體過(guò)程
我們以最前面例子中獲取屬性值時(shí)的調(diào)用棧以實(shí)際運(yùn)行的例子來(lái)看一下整個(gè)運(yùn)行時(shí)獲取屬性值的過(guò)程:
調(diào)用棧不太方便分析, 我們適當(dāng)格式化方便分析, 我們從上圖中從外到內(nèi)的順序來(lái)具體看一下:
格式化后的調(diào)用棧:
//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);
對(duì)應(yīng)的代碼截圖:
格式化后的調(diào)用棧:
//code 2:
framework::reflection::detail::ValueBinder<
framework::math::Vector3,
framework::reflection::detail::TMemberTraits<
double framework::math::Vector3::*
>
>::Getter(framework::math::Vector3 & c);
對(duì)應(yīng)的代碼截圖:
格式化后的調(diào)用棧:
//code 3:
framework::reflection::detail::TMemberTraits<
double framework::math::Vector3::*
>::TBinding<framework::math::Vector3,double &>::Access(framework::math::Vector3 & c)
對(duì)應(yīng)的代碼截圖:
利用多個(gè)模板類的級(jí)聯(lián)和使用,我們最后通過(guò)SimplePropertyImpl<>完成了運(yùn)行時(shí)動(dòng)態(tài)獲取屬性的目的,設(shè)置的過(guò)程與獲取的過(guò)程基本一致,這里不重復(fù)展開了。
九、總結(jié)
通過(guò)多層模板的級(jí)聯(lián),我們完成了運(yùn)行時(shí)動(dòng)態(tài)獲取設(shè)置屬性的功能,另外因?yàn)檎w代碼多利用模板,通過(guò)最后一節(jié)的分析,我們也能發(fā)現(xiàn),整體的性能其實(shí)是比較高的,更多還是依賴模板自身的特性和Tag Dispatch來(lái)完成了相關(guān)的功能。同時(shí),也能發(fā)現(xiàn),如果僅依托c++17的特性,模板之間的關(guān)聯(lián)會(huì)比較弱,整體代碼的維護(hù)和理解會(huì)比較麻煩。后續(xù)我們考慮用c++20的concept重構(gòu)整個(gè)反射庫(kù),到時(shí)再額外輸出相關(guān)的文章了。
- EOF -
聯(lián)系客服