前言:
C++发展到如今已经是一种支持多种范式编程的语言,主要包含过程式、面向对象、函数式、泛型、模板元等。本文写作的目的之一是借助Ponder源码向大家展示模板元编程&泛型范式的编程方式和C++模板元编程是如何与C++普通运行时程序完成无缝衔接的。以及向你展示C++另一种编程方式的乐趣,同时也能让大家熟悉了解底层反射的实现原理。我更加相信程序员是更容易理解代码的,所以文章从源码入手用代码的方式来跟大家进行交流相信会比读文档来的更加有趣味。
读完本文你能收获什么 ? 首先它不会让你突然成为C++编程专家,但它能让你能熟悉很多编程技术实现原理, 让你能读懂与修改你曾不熟悉的技术。
1. 锻炼自己数据抽象与建模能力。在面向对象编程领域相信大家无时无刻不在与class打交道,该能力的培养与锻炼相信能让你的程序更加具有扩展性、可读性、健壮性。
2. 带你深入了解一些元编程技术在实际库中的应用。当然ponder库中所展示的技术也许并不是最新的但相信会有令你足够兴奋的。本文仅仅介绍Ponder库中所使用到的技术。
3. 熟悉反射概念,并对其实现细节有深入了解。能够让你零成本的适应支持反射的其他语言。
4. 一些常用设计模式的使用。
Ponder介绍:
Ponder 是一个为类型提供运行时反射的 C++ 库。它为 C++ 的大多数高级概念(如类、枚举、属性、函数和对象)提供了抽象。通过将所有这些概念包装到抽象结构中,Ponder 为程序提供了额外的灵活性,并允许它们在运行时公开和操作它们的数据结构。
ponder库使用参考文档: ponder: Ponder C++ library documentation
文章结合ponder源码与反射概念,将其对C++主要相关概念(类、类成员属性、类成员函数、类构造函数)的抽象类分别称之为元类类,元属性类,元函数类以及元构造类并主要从这几个抽象类来对ponder源码展开分析。各个抽象类记录着相关概念中的元数据和定义了元数据所具备的行为,当然这里的元数据/信息并非大家所理解的传统意义上的元数据,这里所记录着的元数据是运行时数据且是能够被修改的这点与传统意义上的元信息还是有所区别的。
阅读建议:
由于内容原因,文章篇幅偏长。可以先收藏后在业余时间在持续阅读。
目录
-
Meta Class
MetaClass(元类类)与其他普通类本质上并无区别,普通类定义对象的行为而元类是定义某些类的行为。基于对反射概念的表示,实际上这里的Meta Class本质上还是一个普通类。我们可以先看看 ponder为实现meta class功能所抽象出的UML类图
![](https://i-blog.csdnimg.cn/blog_migrate/43ef143389b38ef086e859995c71a67c.png)
从UML类图可以看出Ponder针对C++ class概念/关键字抽象出的Meta Class类以到达完全具有class概念的类行为,Meta Class类关联了/记录着C++ class中具有的成员属性、成员函数、构造函数、析构函数、基类等概念元信息。同时Meta Class抽象类还关联了ClassManager&ClassBuilder用来管理&构造Meta Class 实例。这里就通过Meta Class完成对C++ class概念/关键字的数据建模。
由于下文中有涉及concept-check,在开始Meta Class源码分析前这里先解释下在泛型/元编程范式中concept-check(概念检查) 一词:
在泛型编程中我们通常要求某些类型在语义上具有某种traits(特征)或者满足某种条件,如类型是否声明了必要的成员函数,是否声明特定的类型等等。 通常将这些要求称之为concept(概念)。concept-check(概念检查)即是检查特定类型是否符合相关特定条件。
meta class源码分析
Meta Class类头文件源码(注: 为优化篇幅以下代码片段并非Class类的全部声明,代码段中仅仅列出了笔者认为能够讲清楚工作流程的主要代码片段)
class PONDER_API Class : public Type
{
PONDER__NON_COPYABLE(Class);
// Structure holding informations about a base metaclass
struct BaseInfo
{
const Class* base;
int offset;
};
typedef std::shared_ptr<Constructor> ConstructorPtr;
typedef std::shared_ptr<Property> PropertyPtr;
typedef std::shared_ptr<Function> FunctionPtr;
typedef std::vector<BaseInfo> BaseList;
typedef std::vector<ConstructorPtr> ConstructorList;
typedef detail::Dictionary<Id, IdRef, PropertyPtr> PropertyTable;
typedef detail::Dictionary<Id, IdRef, FunctionPtr> FunctionTable;
typedef void (*Destructor)(const UserObject&, bool);
typedef UserObject (*UserObjectCreator)(void*);
size_t m_sizeof; // Size of the class in bytes.
TypeId m_id; // Unique type id of the metaclass.
Id m_name; // Name of the metaclass
FunctionTable m_functions; // Table of metafunctions indexed by ID
PropertyTable m_properties; // Table of metaproperties indexed by ID
BaseList m_bases; // List of base metaclasses
ConstructorList m_constructors; // List of metaconstructors
Destructor m_destructor; // Destructor (function able to delete an abstract object)
UserObjectCreator m_userObjectCreator; // Convert pointer of class instance to UserObject
public: // declaration
template <typename T>
static ClassBuilder<T> declare(IdRef id = ponder::IdRef());
template <typename T>
static void undeclare();
pulic: // reflection
IdReturn name() const;
size_t baseCount() const;
const Class& base(size_t index) const;
const Constructor* constructor(size_t index) const;
void destruct(const UserObject &uobj, bool destruct) const;
bool hasFunction(IdRef name) const;
const Function& function(size_t index) const;
const Function& function(IdRef name) const;
FunctionView functions() const;
bool tryFunction(const IdRef name, const Function*& funcRet) const;
bool hasProperty(IdRef name) const;
const Property& property(size_t index) const;
const Property& property(IdRef name) const;
PropertyView properties() const;
bool tryProperty(const IdRef name, const Property*& propRet) const;
private:
template <typename T> friend class ClassBuilder;
friend class detail::ClassManager;
Class(TypeId const& id, IdRef name);
int baseOffset(const Class& base) const;
};
这里我们具体来看看Ponder Meta Class中的具体功能,从Class头文件的接口及其成员属性声明我们可以知道Class类的功能主要有以下几点:
- 为简化Meta Class类的使用成本Meta Class类提供了私有的构造函数不允许客户代码在外部实例化Meta Class对象,同时基于建造者模式提供ClassBuilder模板类,需通过静态模板方法declare来声明一个Meta Class T并通过ClassBuilder实例来进行Meta Class的元信息填充,使得客户代码能够使用链式调用简化Class对象的实例化过程及T类元信息注册过程。
- 记录存储某个类T(T的实例化来自于外部代码调用declare方法时的模板参数)的成员属性、成员函数、构造函数、析构函数、基类、名称等meta信息的集合,从UML类图可以看出这些元信息都是通过ponder库中Property、Function等元类进行了封装来间接表示的。
- 结合下文中的ClassBuilder模板类共同提供对类的meta信息的增删改查等接口。
到这里Meta Class的数据与行为基本就声明完成, 本质上来说并不复杂。
注:空基类Type做类型擦除用,无实际功能。
Type Define
class PONDER_API Type
{
public:
virtual ~Type() {}
};
基于建造者&观察者模式提供ClassBuilder模板类&ClassManager类来管理MetaClass集合与实例化。
ClassBuilder类头文件源码,它通过建造者模式提供了Meta Class的构造过程。
template <typename T>
class ClassBuilder
{
public:
ClassBuilder(Class& target);
template <typename U>
ClassBuilder<T>& base();
template <typename F>
ClassBuilder<T>& property(IdRef name, F accessor);
template <typename F1, typename F2>
ClassBuilder<T>& property(IdRef name, F1 accessor1, F2 accessor2);
template <typename F, typename... P>
ClassBuilder<T>& function(IdRef name, F function, P... policies);
template <typename... A>
ClassBuilder<T>& constructor();
template <template <typename> class U>
ClassBuilder<T>& external();
template <typename... U>
ClassBuilder<T>& operator () (U&&... uds)
{
const std::initializer_list<UserData> il = {uds...};
for (UserData const& ud : il)
userDataStore()->setValue(*m_currentType, ud.getName(), ud.getValue());
return *this;
}
private:
ClassBuilder<T>& addProperty(Property* property);
ClassBuilder<T>& addFunction(Function* function);
Class* m_target;
Type* m_currentType;
};
相信熟悉建造者模式的朋友结合上面ClassBuilder类的声明完全可以看出该类的具体功能了。只不过ponder中提供的ClassBuilder类加上模板与提供的模板方法后让类看起来更加复杂了。由于ponder库将要实现功能原因这里的成员方法形参用来表示T类将要注册的元信息。而元信息与具体类型无关所以必须要用模板参数来表示。这里同样介绍下ClassBuilder的主要实现功能:
- 通过ClassBuilder模板类的声明我们可以看出此类所有接口均为返回自身引用以达到链式访问的目的(基本符合建造者模式的规则),构造函数形参为ClassBuilder类操作的代表类T的MetaClass实例。 base、property、 function、constructor 等模板方法分别提供了类T 需要注册到当前表示自己的MetaClass对象中的元信息。
- base 模板方法为类T提供基类元信息的注册。
- constructor 模板方法的模板参数为模板参数包,模板参数列表为类T的构造函数形参类型列表。最终会将模板参数列表打包进ConstructorImpl Class中。
- property&function 方法中的模板类型形参均会被封装成ponder库中的Property&Function等类实例(即通过Property&Function类型擦除模板通用类型),具体封装过程可以在下文MetaProperty&MetaFunction&MetaConstructor中查阅(会涉及到较多的C++ 模板元编程的内容)。
- external 提供了从类型U(模板类)且U的模板参数为当前T的类型中添加属性或方法。
- operator() 模板方法提供了任意参数数量、类型的形参列表用于将用户数据存储到最后一次注册到Meta Class实例中的元信息对象中。在需要时可以通过元信息实例从TypeUserDataStore对象中获取用户数据。这里笔者有个疑问,通过operator()方法的实现可以看出,参数列表的每个实参必须为可以转换成UserData类型的数据,这样就大大限制了该方法声明模板参数包的威力以及增加了使用者对于模板参数包的理解程度。 是否进行下列方式优化会更好?
// PONDER userdata类的声明&实现 class UserData { public: UserData(IdRef name, Value&& value) : m_name(name), m_value(value) {} IdReturn getName() const { return m_name; } const Value& getValue() const { return m_value; } private: Id m_name; Value m_value; }; // 优化方式1, 取消方法模板参数列表。降低使用者理解程度. 直接要求为形参为UserData类型列表。 ClassBuilder<T>& operator () (const std::vector<UserData*>& uds) { for (auto const& ud : uds) userDataStore()->setValue(*m_currentType, ud->getName(), ud->getValue()); return *this; } // 优化方式2, 对UserData做概念检查。给出更加明确的数据类型要求和出错信息。 // 这里不要求参数类型必须为UserData, 只需要存在getName&getValue方法且返回值分别为Id&Value即可。 // 最大限度的发挥出模板参数包的威力同时给使用者足够的灵活度。 // 注意这里的概念检查并没有对类型限定符做处理。 template <typename... U> struct is_user_data; template <typename UF, typename... U> struct is_user_data<UF, U...> { template<typename UD, typename = void> struct is_user_data_inner { static constexpr bool value = false; }; template<typename UD> struct is_user_data_inner<UD, std::void_t<decltype(&UD::getName), decltype(&UD::getValue)>> { static constexpr bool value = std::is_same_v<decltype(std::declval<UD>().getName(), IdReturn> && std::is_same_v<decltype(std::declval<UD>().getValue(), Value>>; }; static constexpr bool value = is_user_data_inner<UF>::value && is_user_data<U...>::value; }; template <> struct is_user_data<> { static constexpr bool value = true; }; template <typename... U> ClassBuilder<T>& operator () (U&&... uds) { static_assert(is_user_data<U...>::value, "存在不符合规范的参数类型"); setUserData(std::forward<U>(uds)...); return *this; } void setUserData() {} template<typename UF, typename ...U> void setUserData(UF&& ud, U&& ... uds) { userDataStore()->setValue(*m_currentType, ud.getName(), ud.getValue()); setUserData(std::forward<U>(uds)...); }
笔者认为ClassBuilder是个非常重要的类,它将类的具体元信息这里特指成员属性地址、属性get/set函数地址、成员函数地址、基类类型、构造函数形参列表等信息转换为在ponder库中抽象出的具体元信息类进行表示并将这些元信息类实例存储到Meta Class中。这里即完成了C++ class类的表示和信息注册,通过Meta Class实例即可在运行时完成类的信息获取和行为调用即达到了运行时反射的功能。
ClassManager类声明, 它管理着Meta Class的集合并通过观察者模式让外部客户代码能够监听Meta Class实例的添加、删除等行为。
class PONDER_API ClassManager : public ObserverNotifier
{
typedef std::map<TypeId, Class*> ClassTable;
typedef std::map<Id, Class*> NameTable;
public:
typedef View<const Class&, ClassTable::const_iterator> ClassView;
static ClassManager& instance();
Class& addClass(TypeId const& id, IdRef name);
void removeClass(TypeId const& id);
size_t count() const;
const Class& getById(TypeId const& id) const;
const Class* getByIdSafe(TypeId const& id) const;
const Class& getByName(const IdRef name) const;
const Class* getByNameSafe(const IdRef name) const;
bool classExists(TypeId const& id) const;
ClassManager();
~ClassManager();
ClassView getClasses() const;
private:
ClassTable m_classes;
NameTable m_names;
};
通过ClassManager类的声明可以看出此类主要提供如下功能:
- 是一个Meta Class实例的容器,用于存储Meta Class类的所有实例。成员属性m_classes&m_names代表着可通过注册类型的typeid&name操作相对应的 Meta Class实例。
- 提供对Meta Class实例的增删查接口。
- 继承自ObserverNotifier观察者接口, 用于客户代码观察meta class实例的变化行为。
到这里Ponder 库中对于C++ Class 概念的封装就完成了,现在来看看Meta Class的declare静态方法实现&使用示例:
Meta Class declare function impl:
template <typename T>
inline ClassBuilder<T> Class::declare(IdRef name)
{
typedef detail::StaticTypeDecl<T> typeDecl;
Class& newClass =
detail::ClassManager::instance()
.addClass(typeDecl::id(false), name.empty() ? typeDecl::name(false) : name);
newClass.m_sizeof = sizeof(T);
newClass.m_destructor = &detail::destroy<T>;
newClass.m_userObjectCreator = &detail::userObjectCreator<T>;
return ClassBuilder<T>(newClass);
}
通过declare的方法的实现代码typedef detail::StaticTypeDecl<T> typeDecl; 可以看出要声明获取类T的Meta Class对象需特化StaticTypeDecl模板类。Ponder库为我们声明了特化StaticTypeDecl模板类的PONDER_TYPE与PONDER_AUTO_TYPE宏:
template <typename T>
struct StaticTypeDecl
{
static constexpr bool defined = false, copyable = true;
typedef T type;
static TypeId id(bool = true)
{
return T::PONDER_TYPE_NOT_REGISTERED();
}
static const char* name(bool = true)
{
return T::PONDER_TYPE_NOT_REGISTERED();
}
};
#define PONDER_TYPE(...) \
namespace ponder { namespace detail { \
template<> struct StaticTypeDecl<__VA_ARGS__> \
{ \
static TypeId id(bool = true) {return calcTypeId<__VA_ARGS__>();} \
static constexpr const char* name(bool = true) {return #__VA_ARGS__;} \
static constexpr bool defined = true, copyable = true; \
}; \
}}
#define PONDER_AUTO_TYPE(TYPE, REGISTER_FN) \
namespace ponder { namespace detail { \
template<> struct StaticTypeDecl<TYPE> { \
static TypeId id(bool checkRegister = true) { \
if (checkRegister) detail::ensureTypeRegistered(calcTypeId<TYPE>(), REGISTER_FN); \
return calcTypeId<TYPE>(); \
} \
static const char* name(bool checkRegister = true) { \
if (checkRegister) detail::ensureTypeRegistered(calcTypeId<TYPE>(), REGISTER_FN); \
return #TYPE; \
} \
static constexpr bool defined = true, copyable = true; \
}; \
}}
Use Meta Class Sample Code:
// 此示例仅仅演示如何将客户代码类转化为表示其的Meta Class实例对象,不涉及类的其他元信息注册过程,
// 类的其他元信息注册参考下文
struct TestClassBase{};
struct TestClass : TestClassBase{};
void declare() {
ponder::Class::declare<TestClassBase>();
ponder::Class::declare<TestClass >().base<TestClassBase>();
}
// 这里我们需要通过Ponder提供的宏来全特化StaticTypeDecl模板类,
// 不然Meta Class declare方法则编译出错,无法找到StaticTypeDecl<TestClassBase>&StaticTypeDecl<TestClass> Type.
// 当然你也可以不使用Ponder提供的宏,只要提供满足于StaticTypeDecl<T>概念的类型即可。
PONDER_AUTO_TYPE(TestClassBase, &declare)
PONDER_AUTO_TYPE(TestClassBase, &declare)
void test() {
ponder::classByName("TestClass").name() == "TestClass";
ponder::classByName("TestClass") == ponder::classByType<TestClass>();
}
-
Meta Property
这里开始介绍ponder库对类成员属性概念的数据建模(Meta Property)
首先看看ponder实现Meta Property的UML类图
![](https://i-blog.csdnimg.cn/blog_migrate/b5fa51279b121b1082c0ab93261316f0.png)
从上图可以看出Ponder库源码对常见的数据变量类型基于Property基类分别抽象出了SimpleProperty(内置的基本数据类型)、ArrayProperty(集合类型)、UserProperty(自定义类型)、EnumProperty(枚举类型)类分别用来表示不同类型属性的抽象。同时基于工厂模式提供了Meta Property的工厂类(PropertyFactory)用于产生不同的Meta Property实例。下文开始沿着SimpleProperty的实现过程这条线开始进行源码分析其他类型的实现大同小异鉴于篇幅原因就不做介绍了。
Meta Property 元属性信息的表示基类(Property)类声明:
class PONDER_API Property : public Type
{
PONDER__NON_COPYABLE(Property);
public:
virtual ~Property();
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;
virtual void accept(ClassVisitor& visitor) const;
protected:
template <typename T> friend class ClassBuilder;
friend class UserObject;
Property(IdRef name, ValueKind type);
virtual Value getValue(const UserObject& object) const = 0;
virtual void setValue(const UserObject& object, const Value& value) const = 0;
private:
Id m_name; // Name of the property
ValueKind m_type; // Type of the property
};
Property基类功能介绍:
从Property属性基类声明可以看出Property记录着属性概念共有的特性&行为,包括属性名称和属性值种类,且提供属性名称、值种类、是否可读写、值获取、设置共用接口。 get&set接口实现为调用私有成员方法 getValue&setValue ,getValue&setValue为抽象接口最终交由具体子类去实现属性值的获取&设置。
基于访问者模式,Property基类为属性提供了一个accept访问者接口,用于访问具体的属性子对象。
下面开始从ponder源码层面开始介绍ponder是如何把各类的成员属性抽象、构造成统一的Property对象表示的。
在介绍以下内容时,首先先说明下C++11 引入的type_traits即类型特征技术&SFINAE。因为下文的内容会涉及这方面较多的知识,这里介绍下
1. 类型特征(Type_traits)定义了一个基于编译时模板的接口来查询或修改类型的属性。
个人的理解是Type_traits提供了编译时基于类型的类型擦除技术即具有共性的一类类型可以通过同一模板接口来表示。当然实现原理还是利用基于C++ Compiler对于模板重载决议的策略。
2. SFINAE:全称Substitution Failure Is Not An Error直译为替换失败不是错误。
在某些情况下,我们需要确保函数模板or类模板只能被某些特定的类型调用。 SFINAE是一组规则,指定编译器如何在不导致错误的情况下从重载决议中丢弃相关特化。 实现这一点的一种通常方法是借助 std::enable_if当然并非唯一方法。在下文中会大量出现此手法。
下文开始涉及较多的独立于特定变量类型的编程方式即元编程方式,所以说要开始习惯这样的编码方式/范式。
从上文Meta Class ClassBuilder类的介绍中可以看出此类基于建造模式针对C++ Class概念提供了类元信息(类属性、成员方法、基类、构造等)的注册入口方法,其中property方法提供了类成员属性的注册接口。以下为ClassBuilde类property方法的实现, 我们将把该方法作为入口看Ponder是如何通过元编程具现出实际的属性类型及属性实例表示对象(SimpleProperty、ArrayProperty、UserProperty、EnumProperty):
template <typename T>
template <typename F>
ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F accessor)
{
return addProperty(detail::PropertyFactory1<T, F>::create(name, accessor));
}
template <typename T>
template <typename F1, typename F2>
ClassBuilder<T>& ClassBuilder<T>::property(IdRef name, F1 accessor1, F2 accessor2)
{
return addProperty(detail::PropertyFactory2<T, F1, F2>::create(name, accessor1, accessor2));
}
template <typename T>
ClassBuilder<T>& ClassBuilder<T>::addProperty(Property* property)
{
Class::PropertyTable& properties = m_target->m_properties;
properties.erase(property->name());
properties.insert(property->name(), Class::PropertyPtr(property));
m_currentType = property;
return *this;
}
可以看出方法实现中只是简单的通过属性工厂(PropertyFactory1或PropertyFactory2)结构中的静态create方法构造出Property对象。
PropertyFactory1&PropertyFactory2属性工厂类源码:
// typename E as SFINAE
template <typename C, typename T, typename E = void>
struct PropertyFactory1
{
static constexpr PropertyKind kind = PropertyKind::Function;
static Property* create(IdRef name, T accessor)
{
typedef GetSet1<C, FunctionTraits<T>> Accessor; // read-only?
typedef typename Accessor::Access::template Impl<Accessor> PropertyImpl;
return new PropertyImpl(name, Accessor(accessor));
}
};
//*
* 使用SFINAE,通过enable_if在编译时决议待注册的属性访问器accessor类型是否为类数据成员地址类型
*/
template <typename C, typename T>
struct PropertyFactory1<C, T, typename std::enable_if<std::is_member_object_pointer<T>::value>::type>
{
static constexpr PropertyKind kind = PropertyKind::MemberObject;
static Property* create(IdRef name, T accessor)
{
typedef GetSet1<C, MemberTraits<T>> Accessor; // read-only?
typedef typename Accessor::Access::template Impl<Accessor> PropertyImpl;
return new PropertyImpl(name, Accessor(accessor));
}
};
// 为注册属性时提供属性的读写接口版本 accessor1 & accessor2
// 个人理解这里用于SFINAE的模板参数E可以删除,因为PropertyFactory2 并没有偏特化实现
template <typename C, typename F1, typename F2, typename E = void>
struct PropertyFactory2
{
static Property* create(IdRef name, F1 accessor1, F2 accessor2)
{
typedef GetSet2<C, FunctionTraits<F1>> Accessor; // read-write wrapper
typedef typename Accessor::Access::template Impl<Accessor> PropertyImpl;
return new PropertyImpl(name, Accessor(accessor1, accessor2));
}
};
下面开始介绍 PropertyFactory1&2的工作过程:
PropertyFactory1模板类(元函数)大体功能是通过提取property方法访问器形参accessor的类型信息用于在编译期决议出具体用来表示该类的属性的Property子类类型。
property(IdRef name, F accessor)方法形参表示需要一个属性名称和一个属性访问器实参,其中属性访问器是泛型代表访问器可以是类数据成员地址或类数据成员的get方法地址(从PropertyFactory1声明&定义中不难发现)。property方法中会去调用PropertyFactory1模板结构的静态create方法构造Property子类实例,其中PropertyFactory1模板结构参数T表示类属性类型,property模板参数F的具体类型交由PropertyFactory1去决议, 通过std::is_member_object_pointer<T> type_traits来确定,如果T为成员方法地址则使用PropertyFactory1正常版本,如果为成员属性类型则使用下面的PropertyFactory1偏特化版本。
下面通过一个具体示例来演示编译器实例化PropertyFactory1的具体类型:
struct TestClass {
int getValue() {return value_;}
int value_ {0};
};
void decalare() {
ponder::Class::declare<TestClass>().property("value", &TestClass::value_);
ponder::Class::declare<TestClass>().property("getValue", &TestClass::getValue);
}
PONDER_AUTO_TYPE(TestClass , &declare)
1. 由于方法accessor实参&TestClass::value_ 类型为 int (TestClass::*), 则property方法中的源码 detail::PropertyFactory1<T, F>::create(name, accessor)被具现为detail::PropertyFactory1<TestClass, int (TestClass::*)>::create(name, accessor), 其中PropertyFactory1模板被实例化为下面版本,因为property方法实参accessor 类型int (TestClass::*) 满足std::is_member_object_pointer traits。
template <typename C, typename T>
struct PropertyFactory1<C, T, typename std::enable_if<std::is_member_object_pointer<T>::value>::type>;
2. 由于方法accessor实参&TestClass::getValue 类型 int (TestClass::*)(), 同样的由于实参accessor 类型int (TestClass::*)() 不满足std::is_member_object_pointer traits,所以编译器将PropertyFactory1为下面类型:
template <typename C, typename T, typename E = void>
struct PropertyFactory1;
这样就区分出不同的属性类型,用于后面具体业务的实现。现在来看看PropertyFactory1类针对不同的属性类型的处理流程:
属性类型为类的成员属性地址类型时, PropertyFactory1结构将类属性类型重定义为以下类型:
typedef GetSet1<C, MemberTraits<T>> Accessor;
重定义GetSet1<C, MemberTraits<T>>类型为Accessor 类型。
下面将类型GetSet1<C, MemberTraits<T>>一步步来进行分析:
MemberTraits 模板结构声明&定义,
MemberTraits 类型特征接口用于类数据成员地址类型的操作, 接口声明定义如下:
template <typename T>
struct MemberTraits;
template <typename C, typename T>
struct MemberTraits<T(C::*)>
{
typedef T(C::*BoundType); // full type inc ref
typedef T ExposedType; // the type exposed inc refs
typedef TypeTraits<ExposedType> ExposedTraits;
typedef typename ExposedTraits::DataType DataType; // raw type or container
static constexpr bool isWritable = !std::is_const<typename ExposedTraits::DereferencedType>::value;
typedef typename ExposedTraits::DereferencedType AccessType;
template <typename BC, typename A>
class Binding
{
public:
typedef BC ClassType;
typedef A AccessType;
Binding(const BoundType& d) : data(d) {}
AccessType access(ClassType& c) const { return c.*data; }
private:
BoundType data;
};
};
从MemberTraits的声明可以看出,该traits只处理类型为类的数据成员地址类型,所以模板参数T一定是类型(Type(Type::*))
具体Property的类型决议都是依赖Ponder中的各个traits模板类来进行的,下文会涉及较多的type_traits模板类。这里你可以把这些traits看做是对某个类型做类型拆分、组合、增删限定符等各种对类型进行计算的操作。
MemberTraits 类型特征接口提供了对于类数据成员地址类型的一些查询操作
- BoundType: 代表类数据成员地址类型全类型即原本类型,用于返回MemberTraits的模板参数T,方便外部访问。
- ExposedType: 表示成员属性变量类型
- ExposedTraits: 重定义TypeTraits<ExposedType> 类型, 同理TypeTraits类型特征接口声明了一些对类型的操作。具体解释继续阅读下文针对TypeTraits的分析。
- DataType:通过TypeTraits<ExposedType> 类型特征接口查询获取类成员属性变量类型ExposedType的原始类型,即删除类数据成员类型的指针、引用、const信息。如果类型为智能指针则获取智能指针代表的实际类型、获取数组实际类型。 这些类型信息都是通过TypeTraits类型特征中的DataType traits 获取的。
- isWritable: 类的成员属性变量类型是否为const
- AccessType: 通过TypeTraits<ExposedType> 类型特征接口重定义类数据成员类型的解引用类型即如果类数据成员为 指针T*或引用 T& 则AccessType 代表 T类型
- Binding: 用于保存类数据成员地址,同时提供访问地址方法用于对类对象成员属性进行读写。
TypeTraits&DataType 类型特征源码:
template <typename T, typename E = void>
struct TypeTraits
{
static constexpr ReferenceKind kind = ReferenceKind::Instance;
typedef T Type;
typedef T& ReferenceType;
typedef T* PointerType;
typedef T DereferencedType;
static_assert(!std::is_void<T>::value, "Incorrect type details");
typedef typename DataType<T>::Type DataType;
static constexpr bool isWritable = !std::is_const<DereferencedType>::value;
static constexpr bool isRef = false;
static inline ReferenceType get(void* pointer) { return *static_cast<T*>(pointer); }
static inline PointerType getPointer(T& value) { return &value; }
static inline PointerType getPointer(T* value) { return value; }
};
// void
template <>
struct TypeTraits<void>
{
static constexpr ReferenceKind kind = ReferenceKind::None;
typedef void T;
typedef T* ReferenceType;
typedef T* PointerType;
typedef T DereferencedType;
typedef typename DataType<T>::Type DataType;
static constexpr bool isWritable = false;
static constexpr bool isRef = false;
static inline ReferenceType get(void* pointer) { return 0; }
static inline PointerType getPointer(T* value) { return value; }
};
// Raw pointers
template <typename T>
struct TypeTraits<T*>
{
static constexpr ReferenceKind kind = ReferenceKind::Pointer;
typedef T* Type;
typedef T* ReferenceType;
typedef T* PointerType;
typedef T DereferencedType;
typedef typename DataType<T>::Type DataType;
static constexpr bool isWritable = !std::is_const<DereferencedType>::value;
static constexpr bool isRef = true;
static inline ReferenceType get(void* pointer) {return static_cast<T*>(pointer);}
static inline PointerType getPointer(T& value) {return &value;}
static inline PointerType getPointer(T* value) {return value;}
};
// References
template <typename T>
struct TypeTraits<T&>
{
static constexpr ReferenceKind kind = ReferenceKind::Reference;
typedef T& Type;
typedef T& ReferenceType;
typedef T* PointerType;
typedef T DereferencedType;
typedef typename DataType<T>::Type DataType;
static constexpr bool isWritable = !std::is_const<DereferencedType>::value;
static constexpr bool isRef = true;
static inline ReferenceType get(void* pointer) {return *static_cast<T*>(pointer);}
static inline PointerType getPointer(T& value) {return &value;}
static inline PointerType getPointer(T* value) {return value;}
};
template <class P, typename T>
struct SmartPointerReferenceTraits
{
typedef P Type;
static constexpr ReferenceKind kind = ReferenceKind::SmartPointer;
typedef T& ReferenceType;
typedef P PointerType;
typedef T DereferencedType;
typedef typename DataType<T>::Type DataType;
static constexpr bool isWritable = !std::is_const<DereferencedType>::value;
static constexpr bool isRef = true;
static inline ReferenceType get(void* pointer) {return *static_cast<P*>(pointer);}
static inline PointerType getPointer(P& value) {return get_pointer(value);}
};
// std::shared_ptr<>
template <typename T>
struct TypeTraits<std::shared_ptr<T>>
: public SmartPointerReferenceTraits<std::shared_ptr<T>,T> {};
// Built-in arrays []
template <typename T, size_t N>
struct TypeTraits<T[N], typename std::enable_if<std::is_array<T>::value>::type>
{
static constexpr ReferenceKind kind = ReferenceKind::BuiltinArray;
typedef T Type[N];
typedef typename DataType<T>::Type DataType;
typedef T(&ReferenceType)[N];
typedef T* PointerType;
typedef T DereferencedType[N];
static constexpr size_t Size = N;
static constexpr bool isWritable = !std::is_const<T>::value;
static constexpr bool isRef = false;
};
//
template <typename T, typename E = void>
struct DataType
{
typedef T Type;
};
// const
template <typename T>
struct DataType<const T> : public DataType<T> {};
template <typename T>
struct DataType<T&> : public DataType<T> {};
template <typename T>
struct DataType<T*> : public DataType<T> {};
template <typename T, size_t N>
struct DataType<T[N]> : public DataType<T> {};
这里对TypeTraits 类型特征模板类做下大致的介绍:
通过源码可以看出有TypeTraits工具的存在大大的简化了我们对于类成员变量类型的操作,无论类型是原始Raw、指针、引用、智能指针、数组、void都有了可以按统一方式处理的逻辑。某种程序上我们可以将TypeTraits当作是一个智能数据类型。TypeTraits实现原理并不复杂,它仅仅利用模板特化技术针对T&、T*、void、T[N]、std::shared_ptr<T>这些类型做了特殊处理,解决了引用的引用,指针的指针、智能指针Raw Type获取等问题。
到这里我们清楚了MemberTraits的作用,下面接着看GetSet1实现
GetSet1源码
template <class C, typename TRAITS>
class GetSet1
{
public:
typedef TRAITS PropTraits;
typedef C ClassType;
typedef typename PropTraits::ExposedType ExposedType;
typedef typename PropTraits::ExposedTraits TypeTraits;
typedef typename PropTraits::DataType DataType; // raw type or container
static constexpr bool canRead = true;
static constexpr bool canWrite = PropTraits::isWritable;
typedef AccessTraits<PropTraits> Access;
typedef typename Access::template ValueBinder<ClassType> InterfaceType;
InterfaceType m_interface;
GetSet1(typename PropTraits::BoundType getter)
: m_interface(typename InterfaceType::Binding(getter))
{}
};
正如命名而言可以看出此结构代表着类成员属性Get&Set操作,它的构造函数需要类成员地址用来传递到Binding实例中,它是操作类成员属性的间接层。
GetSet1的模板参数C代表着类成员所在的类类型,TRAITS代表MemberTraits类型。
GetSet1同样基于MemberTraits类型做了些类型的处理操作
- PropTraits: 重定义MemberTraits为PropTraits
- ClassType: 重定义注册类类型, 待表着类成员所在的类类型
- ExposedType& ExposedTraits & DataType: 简单从MemberTraits类型特征接口下获取相关类型做重定义方便外部通过GetSet1直接获取,而不需要间接获取。是否也可以间接获取用来简化GetSet1返回的类型。(●'◡'●)
- canRead: 代表此属性可读,属性一定是可读的
- canWrite: 通过MemberTraits 决定属性是否可写。const不可写
- typedef AccessTraits<PropTraits> Access : 重定义AccessTraits<PropTraits> 类型为Access
AccessTrait类型特征接口个人认为比较重要,由它来决议具体的Property子类类型即提供具体Property子类类型的查询操作。 从Meta Property UML类图可以看到Property所有子类型别。
AccessTrait类型特征接口源码:
// typename E as SFINAE
template <typename PT, typename E = void>
struct AccessTraits
{
static constexpr PropertyAccessKind kind = PropertyAccessKind::Simple;
template <class C>
using ValueBinder = ValueBinder<C, PT>;
template <class C>
using ValueBinder2 = ValueBinder2<C, PT>;
template <typename A>
using Impl = SimplePropertyImpl<A>;
};
//*
* 使用SFINAE,如果类数据成员类型DereferencedType通过std::enable_if 决议为enum type
*/
template <typename PT>
struct AccessTraits<PT,
typename std::enable_if<std::is_enum<typename PT::ExposedTraits::DereferencedType>::value>::type>
{
static constexpr PropertyAccessKind kind = PropertyAccessKind::Enum;
template <class C>
using ValueBinder = ValueBinder<C, PT>;
template <class C>
using ValueBinder2 = ValueBinder2<C, PT>;
template <typename A>
using Impl = EnumPropertyImpl<A>;
};
//*
* 使用SFINAE,如果类数据成员类型DereferencedType通过enable_if&ArrayMapper类型特征接口获取到isArray标
志为真
*/
template <typename PT>
struct AccessTraits<PT,
typename std::enable_if<ponder_ext::ArrayMapper<typename PT::ExposedTraits::DereferencedType>::isArray>::type>
{
static constexpr PropertyAccessKind kind = PropertyAccessKind::Container;
typedef ponder_ext::ArrayMapper<typename PT::ExposedTraits::DereferencedType> ArrayTraits;
template <class C>
class ValueBinder : public ArrayTraits
{
public:
typedef typename PT::ExposedTraits::DereferencedType ArrayType;
typedef C ClassType;
typedef typename PT::AccessType& AccessType;
using Binding = typename PT::template Binding<ClassType, AccessType>;
ValueBinder(const Binding& a) : m_bound(a) {}
AccessType getter(ClassType& c) const {return m_bound.access(c);}
bool setter(ClassType& c, AccessType v) const {
return this->m_bound.access(c) = v, true;
}
protected:
Binding m_bound;
};
template <typename A>
using Impl = ArrayPropertyImpl<A>;
};
//*
* 使用SFINAE,如果类数据成员类型DereferencedType被
*/
template <typename PT>
struct AccessTraits<PT,
typename std::enable_if<hasStaticTypeDecl<typename PT::ExposedTraits::DereferencedType>()
&& !std::is_enum<typename PT::ExposedTraits::DereferencedType>::value>::type>
{
static constexpr PropertyAccessKind kind = PropertyAccessKind::User;
template <class C>
using ValueBinder = typename std::conditional<
std::is_pointer<typename PT::ExposedType>::value, InternalRefBinder<C, PT>, ValueBinder<C, PT>
>::type;
template <class C>
using ValueBinder2 = typename std::conditional<
std::is_pointer<typename PT::ExposedType>::value, InternalRefBinder2<C, PT>, ValueBinder2<C, PT>
>::type;
template <typename A>
using Impl = UserPropertyImpl<A>;
};
AccessTraits 的实现原理是利用SFINAE特性,来决议出具体不同类成员变量类型所对应的Property子类抽象类型。(SimpleProperty、ArrayProperty、UserProprety、EunmProperty)
由上文重载特化AccessTraits类型特征版本可以看出AccessTraits模板参数PT为MemberTraits类型,同时AccessTraits偏特化出3种属性版本:
1. 默认提供SimpleProperty类型
2. std::enable_if<std::is_enum<typename PT::ExposedTraits::DereferencedType>::value: 其中PT::ExposedTraits::DereferencedType即通过MemberTraits来获取类数据成员变量原始类型。如果类数据成员类型为枚举则使用此Traits,提供Impl的类型为EnumProperty
3. ponder_ext::ArrayMapper<typename PT::ExposedTraits::DereferencedType>::isArray 数组版本
4. hasStaticTypeDecl<typename PT::ExposedTraits::DereferencedType>()
&& !std::is_enum<typename PT::ExposedTraits::DereferencedType>::value 用户自定义类型且通过ponder注册过该类型版本
至此通过类数据成员变量类型使用哪个Property子类类型由AccessTraits类型特征在编译时决议完成。 同时AccessTraits还提供ValueBinder&ValueBinder2类型,该类型为ValueBinder&ValueBinder2模板类的类型重定义。
从AccessTraits类型特征接口源码可以看出,Normal& 枚举偏特化版本提供相同的ValueBinder模板类类型,而数组与自定义属性类型则重新定义了ValueBinder。
ValueBinder源码:
template <class C, typename PropTraits>
class ValueBinder
{
public:
typedef C ClassType;
typedef typename std::conditional<PropTraits::isWritable,
typename PropTraits::AccessType&, typename PropTraits::AccessType>::type AccessType;
typedef typename std::remove_reference<AccessType>::type SetType;
using Binding = typename PropTraits::template Binding<ClassType, AccessType>;
static_assert(!std::is_pointer<AccessType>::value, "Error: Pointers not handled here");
ValueBinder(const Binding& b) : m_bound(b) {}
AccessType getter(ClassType& c) const { return m_bound.access(c); }
Value getValue(ClassType& c) const {
if constexpr (PropTraits::isWritable)
return UserObject::makeRef(getter(c));
else
return UserObject::makeCopy(getter(c));
}
bool setter(ClassType& c, SetType v) const {
if constexpr (PropTraits::isWritable)
return this->m_bound.access(c) = v, true;
else
return false;
}
bool setter(ClassType& c, Value const& value) const {
return setter(c, value.to<SetType>());
}
protected:
Binding m_bound;
};
template <class C, typename PropTraits>
class ValueBinder2 : public ValueBinder<C, PropTraits>
{
typedef ValueBinder<C, PropTraits> Base;
public:
template <typename S>
ValueBinder2(const typename Base::Binding& g, S s) : Base(g), m_set(s) {}
bool setter(typename Base::ClassType& c, typename Base::SetType v) const {
return m_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)> m_set;
};
ValueBinder 封装类成员属性地址变量的直接层,可直接操作成员属性的读写。
由前文推导,ValueBinder模板参数C代表注册类类型,PropTraits参数代表MemberTraits类型特征。基于MemberTraits类型特征 ValueBinder分别获取出了类数据成员的解引用类型AccessType同时判断了MemberTraits下的isWritable标志是否为真,如果为真则为AccessType类型添加引用最后继续通过重定义提供出AccessType类型。获取MemberTraits类型特征下的Binding类型(前文介绍过)重定义为Binding类型。
ValueBinder 添加了Binding类型作为其唯一成员,同时提供对于Binding成员操作的读写接口。所以对于对象的成员操作都是由ValueBinder对象来完成。
ValueBinder2继承自ValueBinder 增加了
static_assert(!std::is_pointer<AccessType>::value, "Error: Pointers not handled here"); 代表着ValueBinder 无法处理指针数据成员类型。 如果说绑定的类数据成员为指针类型则无法处理编译会报错,可以通过以下方式修复。
在AccessTraits类型特征接口下重定义ValueBinder类型时做判断.
将:
template <class C> using ValueBinder = ValueBinder<C, PT>;
修改为:
template<class C>
using ValueBinder = std::conditional_t<!std::is_pointer_v<typename PT::ExposedType>,
ValueBinder<C, PT>,
InternalRefBinder<C, PT>>;
如果数据成员类型为指针类型则ValueBinder类型代表着InternalRefBinder类型, InternalRefBinder模板类对指针类型做了特别处理。
通过以上源码分析个人认为是否可以取消MemberTraits类型特征所有提供的Binding类型,使得MemberTraits更加符合type_traits特征也简化ValueBinder与代码复杂度以及编译时类型推导过程?
修改后的valuebinder
template <class C, typename PropTraits>
class ValueBinder
{
public:
typedef C ClassType;
typedef typename std::conditional<PropTraits::isWritable,
typename PropTraits::AccessType&, typename PropTraits::AccessType>::type AccessType;
typedef typename std::remove_reference<AccessType>::type SetType;
using Binding = typename PropTraits::BoundType;
static_assert(!std::is_pointer<AccessType>::value, "Error: Pointers not handled here");
ValueBinder(const Binding& b) : m_bound(b) {}
AccessType getter(ClassType& c) const { return c.*m_bound; }
Value getValue(ClassType& c) const {
if constexpr (PropTraits::isWritable)
return UserObject::makeRef(getter(c));
else
return UserObject::makeCopy(getter(c));
}
bool setter(ClassType& c, SetType v) const {
if constexpr (PropTraits::isWritable)
return this->c.*m_bound = v, true;
else
return false;
}
bool setter(ClassType& c, Value const& value) const {
return setter(c, value.to<SetType>());
}
protected:
Binding m_bound;
};
同样我们选取AccessTraits Normal 版本,即类数据成员变量类型非数组、用户自定义类型、枚举的情况分支继续深入分析Property实例构造封装过程。特化版本与正常版本逻辑十分相似。
继续PropertyFactory1静态方法Create
typedef typename Accessor::Access::template Impl<Accessor> PropertyImpl;
重定义Accessor::Access::template Impl<Accessor> 类型为 PropertyImpl 类型
有了以上源码分析,我们清楚的知道Accessor为GetSet1类型。 GetSet1提供了AccessTraits类型的获取即由Access类型表示(重定义),AccessTraits类型特征提供/返回了Impl类型的获取,Impl类型的声明即为具体的Property子类类型。 最终重定义Impl 类型即为具体的Property子类类型。
return new PropertyImpl(name, Accessor(accessor));
最后PropertyFactory1 静态方法构造Property实例并返回。 Accessor(GetSet1 )类型 通过AccessTraits提供的ValueBinder 来存储包装类数据成员地址变量。Accessor实例交由Property对象保管,这样Property子类即可通过Accessor自由的操作类数据成员地址进行赋值or值获取。
以上便是选取添加类成员地址时的Property实例构造过程,现在我们选择成员方法作为添加属性时的逻辑。由于逻辑十分相似,只是类型操作traits的变化,所以很多内容都是大致介绍下。
通过PropertyFactory1的声明可以看出 当添加类成员属性元信息时如果元信息为类数据成员类型时会选择PropertyFactory1偏特化版本:
template <typename C, typename T>
struct PropertyFactory1<C, T, typename std::enable_if<std::is_member_object_pointer<T>::value>::type> {};
否则会选择非偏特化版本:
template <typename C, typename T, typename E = void>
struct PropertyFactory1
{
static constexpr PropertyKind kind = PropertyKind::Function;
static Property* create(IdRef name, T accessor)
{
typedef GetSet1<C, FunctionTraits<T>> Accessor; // read-only?
typedef typename Accessor::Access::template Impl<Accessor> PropertyImpl;
return new PropertyImpl(name, Accessor(accessor));
}
};
这里看到与偏特化版本相比除重定义GetSet1类型时的第二个模板参数不同,其余均同。 这里我们看看由MemberTraits换为FunctionTraits之后的逻辑走向。
FunctionTraits声明源码:
typename E as SFINAE
template <typename T, typename E = void>
struct FunctionTraits
{
static constexpr FunctionKind kind = FunctionKind::None;
};
/*
* Specialization for native callable types (function and function pointer types)
* - We cannot derive a ClassType from these as they may not have one. e.g. int get()
*/
template <typename T>
struct FunctionTraits<T,
typename std::enable_if<std::is_function<typename std::remove_pointer<T>::type>::value>::type>
{
static constexpr FunctionKind kind = FunctionKind::Function;
typedef typename function::FunctionDetails<typename std::remove_pointer<T>::type> Details;
typedef typename Details::FuncType BoundType;
typedef typename Details::ReturnType ExposedType;
typedef TypeTraits<ExposedType> ExposedTraits;
static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value
&& !std::is_const<typename ExposedTraits::DereferencedType>::value;
typedef typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type AccessType;
typedef typename ExposedTraits::DataType DataType;
typedef typename Details::DispatchType DispatchType;
template <typename C, typename A>
class Binding
{
public:
typedef C ClassType;
typedef A AccessType;
Binding(BoundType d) : data(d) {}
AccessType access(ClassType& c) const {return (*data)(c);}
private:
BoundType data;
};
};
/*
* Specialization for native callable types (member function types)
*/
template <typename T>
struct FunctionTraits<T, typename std::enable_if<std::is_member_function_pointer<T>::value>::type>
{
static constexpr FunctionKind kind = FunctionKind::MemberFunction;
typedef typename function::MethodDetails<T> Details;
typedef typename Details::FuncType BoundType;
typedef typename Details::ReturnType ExposedType;
typedef TypeTraits<ExposedType> ExposedTraits;
static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value && !Details::isConst;
typedef typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type AccessType;
typedef typename ExposedTraits::DataType DataType;
typedef typename Details::DispatchType DispatchType;
template <typename C, typename A>
class Binding
{
public:
typedef C ClassType;
typedef A AccessType;
Binding(BoundType d) : data(d) {}
AccessType access(ClassType& c) const {return (c.*data)();}
private:
BoundType data;
};
};
/**
* Specialization for functors (classes exporting a result_type type, T operator() ).
*/
template <typename T>
struct FunctionTraits<T, typename std::enable_if<std::is_bind_expression<T>::value>::type>
{
static constexpr FunctionKind kind = FunctionKind::BindExpression;
typedef function::CallableDetails<T> Details;
typedef typename Details::FuncType BoundType;
typedef typename Details::ReturnType ExposedType;
typedef TypeTraits<ExposedType> ExposedTraits;
static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value
&& !std::is_const<typename ExposedTraits::DereferencedType>::value;
typedef typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type AccessType;
typedef typename ExposedTraits::DataType DataType;
typedef typename Details::DispatchType DispatchType;
template <typename C, typename A>
class Binding
{
public:
typedef C ClassType;
typedef A AccessType;
Binding(BoundType d) : data(d) {}
AccessType access(ClassType& c) const {return data(c);}
private:
BoundType data;
};
};
/**
* Specialization for function wrappers (std::function<>).
*/
template <typename T>
struct FunctionTraits<T,
typename std::enable_if<function::IsCallable<T>::value
&& function::IsFunctionWrapper<T>::value>::type>
{
static constexpr FunctionKind kind = FunctionKind::FunctionWrapper;
typedef function::CallableDetails<T> Details;
typedef typename Details::FuncType BoundType;
typedef typename Details::ReturnType ExposedType;
typedef TypeTraits<ExposedType> ExposedTraits;
static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value
&& !std::is_const<typename ExposedTraits::DereferencedType>::value;
typedef typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type AccessType;
typedef typename ExposedTraits::DataType DataType;
typedef typename Details::DispatchType DispatchType;
template <typename C, typename A>
class Binding
{
public:
typedef C ClassType;
typedef A AccessType;
Binding(BoundType d) : data(d) {}
AccessType access(ClassType& c) const {return data(c);}
private:
BoundType data;
};
};
/**
* Specialization for lambda functions ([](){}).
*/
template <typename T>
struct FunctionTraits<T,
typename std::enable_if<function::IsCallable<T>::value
&& !function::IsFunctionWrapper<T>::value>::type>
{
static constexpr FunctionKind kind = FunctionKind::Lambda;
typedef function::CallableDetails<T> Details;
typedef T BoundType;
typedef typename Details::ReturnType ExposedType;
typedef TypeTraits<ExposedType> ExposedTraits;
static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value
&& !std::is_const<typename ExposedTraits::DereferencedType>::value;
typedef typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type AccessType;
typedef typename ExposedTraits::DataType DataType;
typedef typename Details::DispatchType DispatchType;
template <typename C, typename A>
class Binding
{
public:
typedef C ClassType;
typedef A AccessType;
Binding(BoundType d) : data(d) {}
AccessType access(ClassType& c) const {return data(c);}
private:
BoundType data;
};
};
FunctionTraits的实现原理同样利用SFINAE原则,即让编译器在上述所有的模板重载中解析/实例化出最恰当的模板类。
由源码我们也可以看出 FunctionTraits 类型特征提供的类型与MemberTraits基本相同,这样才能保证GetSet1在处理MemberTraits&FunctionTraits 逻辑一致。FunctionTraits 重载版本较多,对于全局函数、成员函数、std::function、lambda、可调用对象、std::bind 等都做了相关处理。
不同点在于FunctionTraits中的Binding类型修改为对函数地址和可调用对象的封装。其余逻辑均同。
至此就完成了类元属性的单参数构造过程, 而PropertyFactory2 这里鉴于篇幅就不做过多介绍。 逻辑基本也差不多只不过修改为使用AccessTraits提供的ValueBinder2类型。同时ClassBuilder property方法的两个形参分别代表属性的读写函数地址。
上文纯文字讲述太多,以下通过一些示例加图像来描述上文中类元属性实例化过程以及编译器类型推导过程:
use example:
struct TestTag {
bool b;
int i;
float f;
int getInt() { return i; }
};
void declare()
{
ponder::Class::declare<TestTag>("TestTag ").property("b",&TestTag ::b).property("f", &TestTag::f).property("get_i", &TestTag ::getInt);
}
PONDER_AUTO_TYPE(TestTag , &TestTag::declare)
展示property("b",&TestTag ::b) 调用C++ 编译器基于各traits对类型的实例化过程:
到这里就完成了Ponder 对Meta Property的实现过程分析。
-
Meta Function
meta function概念:
- 调用某个程序的所有其他函数的函数; 唯一可以独立调用的函数。
- 在编译时执行实体之间映射的构造。
- 一组产生相关含义的语法系统。
ponder为运行时反射库,并不是从语言(编译器)层面来支持反射。 并不完全具备上述概念所有特征但也以上述概念为指导,主要功能还是抽象成具体函数对象来表示,通过Function类来封装抽象函数信息进而达到调用具体函数的目的。
接下来我们看看ClassBuilder是如何注册Meta Function的:
ClassBuilder function方法实现源码:
template <typename T>
template <typename F, typename... P>
ClassBuilder<T>& ClassBuilder<T>::function(IdRef name, F function, P... policies)
{
// Construct and add the metafunction
return addFunction(detail::newFunction(name, function, policies...));
}
形参介绍:
name: 注册的Meta Function名称
function: 注册的可调用类型对象(函数地址、成员函数地址、静态成员函数地址、全局函数地址、lambda、operator() ,std::function 等)
policies: 返回值策略类型参数包
function函数实现分析:
detail::newFunction(name, function, policies...) 构造Function实例,最终的可调用类型对象通过Function类型封装进而达到擦除具体可调用类型信息的目的。
newFunction 函数源码:
template <typename F, typename... P>
static inline Function* newFunction(IdRef name, F function, P... policies)
{
typedef detail::FunctionTraits<F> FuncTraits;
static_assert(FuncTraits::kind != FunctionKind::None, "Type is not a function");
return new FunctionImpl<FuncTraits, F, P...>(name, function, policies...);
}
函数实现比较简单,转发ClassBuilder function方法形参用于构造Function子类FunctionImpl实例。同时通过FunctionTraits类型特征接口检查形参function可调用对象类型的合法性。
元函数信息表示基类Function声明:
class PONDER_API Function : public Type
{
PONDER__NON_COPYABLE(Function);
public:
virtual ~Function();
IdReturn name() const;
FunctionKind kind() const { return m_funcType; }
ValueKind returnType() const;
policy::ReturnKind returnPolicy() const { return m_returnPolicy; }
virtual size_t paramCount() const = 0;
virtual ValueKind paramType(size_t index) const = 0;
virtual void accept(ClassVisitor& visitor) const;
const void* getUsesData() const {return m_usesData;}
protected:
// FunctionImpl inherits from this and constructs.
Function(IdRef name) : m_name(name) {}
Id m_name; // Name of the function
FunctionKind m_funcType; // Kind of function
ValueKind m_returnType; // Runtime return type
policy::ReturnKind m_returnPolicy; // Return policy
const void *m_usesData;
};
Function基类声明的接口&数据成员基本囊括了一个元函数大部分特征信息了。下面大概介绍下方法作用:
name方法返回元函数的名称。
kind 方法返回元函数kind, 由上节介绍的FunctionTraits类型特征中可获取。
returnType 方法提供元函数签名返回值类型
returnPolicy 方法提供返回值策略kind
paramCount¶mType 分表表示函数签名形参数量&指定索引处形参值类型。 均为抽象接口交由具体子类去实现。
accept 基于访问者模式提供对自身对象的访问能力
Function成员属性注释中已经有了比较明确的说明。
m_usesData 万能指针具体存储内容交由下文的子类去讲解(由子类去初始化)。
Function子类(FunctionImpl)声明&定义:
template <typename T, typename F, typename... P>
class FunctionImpl : public Function
{
typedef T FuncTraits;
typedef std::tuple<P...> FuncPolicies;
static constexpr size_t c_nParams =
std::tuple_size<typename FuncTraits::Details::ParamTypes>::value;
std::array<FunctionParamInfo, c_nParams> m_paramInfo;
public:
FunctionImpl(IdRef name, F function, P... policies) : Function(name)
{
m_name = name;
m_funcType = FuncTraits::kind;
m_returnType = mapType<typename FuncTraits::ExposedType>();
m_returnPolicy = ReturnPolicy<typename FuncTraits::ExposedType, P...> ::kind;
m_paramInfo = FunctionApplyToParams<typename FuncTraits::Details::ParamTypes,
FunctionMapParamsToValueKind<c_nParams>>::foreach();
Function::m_usesData = &m_userData;
processUses<uses::Uses::eRuntimeModule>(m_name, function);
PONDER_IF_LUA(processUses<uses::Uses::eLuaModule>(m_name, function);)
}
private:
FunctionImpl(const FunctionImpl&) = delete;
uses::Uses::PerFunctionUserData m_userData;
template <int M>
void processUses(IdRef name, F function)
{
typedef typename std::tuple_element<M, uses::Uses::Users>::type Processor;
std::get<M>(m_userData) =
Processor::template perFunction<F, T, FuncPolicies>(name, function);
}
size_t paramCount() const override { return c_nParams; }
ValueKind paramType(size_t index) const override
{
// Make sure that the index is not out of range
if (index >= c_nParams)
PONDER_ERROR(OutOfRange(index, c_nParams));
return m_paramInfo[index].m_valueType;
}
};
父类成员属性由子类FunctionImpl构造函数初始化,构造函数中对于processUses模板方法调用即去实例化函数对象的封装对象FunctionCaller,同时将FunctionCaller实例保存在m_usesData万能指针中。
processUses 简单的调用静态perFunction方法构造FunctionCaller实例, perFunction方法源码:
struct RuntimeUse
{
/// Factory for per-function runtime data
template <typename F, typename FTraits, typename Policies_t>
static runtime::detail::FunctionCaller* perFunction(IdRef name, F function)
{
return new runtime::detail::FunctionCallerImpl<F, FTraits, Policies_t>(name, function);
}
};
perFunction方法只是简单的new 一个FunctionCaller对象,下面继续看看可调用对象(元函数xin'xi)的抽象者FunctionCaller的源码实现:
FunctionCaller声明&定义
class FunctionCaller
{
public:
FunctionCaller(const IdRef name) : m_name(name) {}
virtual ~FunctionCaller() {}
FunctionCaller(const FunctionCaller&) = delete; // no copying
const IdRef name() const { return m_name; }
virtual Value execute(const Args& args) const = 0;
private:
const IdRef m_name;
};
FunctionCaller为可调用对象信息封装类的基类是个抽象类存储可调用对象(元函数)名称。以及提供一个execute抽象方法交由具体子类去重写。execute 用于执行封装的可调用对象。
FunctionCaller 具体子类FunctionCallerImpl声明&定义:
template <typename F, typename FTraits, typename FPolicies>
class FunctionCallerImpl final : public FunctionCaller
{
public:
FunctionCallerImpl(IdRef name, F function)
: FunctionCaller(name)
, m_function(function)
{}
private:
typedef typename FTraits::Details::FunctionCallTypes CallTypes;
typedef FunctionWrapper<typename FTraits::ExposedType, CallTypes> DispatchType;
typename DispatchType::Type m_function; // Object containing the actual function to call
Value execute(const Args& args) const final
{
return DispatchType::template
call<decltype(m_function), FTraits, FPolicies>(m_function, args);
}
};
m_function 存储可调用对象。 m_function 类型最终会由DispatchType 类型特征决定。 DispatchType::Type 为std::function。所以任何可调用对象最终都会由FunctionCallerImpl成员属性m_function且类型转为std::function 来保存。
execute方法重写父类抽象方法。最终通过一些type_traits手段会去实际调用存储的注册的可调用对象(元函数)。
到这里ponder就完成了Meta Function的注册过程。这里仅仅是保存了注册Meta Function的信息, 通过Function&FunctionImpl声明我们发现并没有提供Meta Fuction的call方法。那怎么调用Meta Function呢?答案是通过全局call方法或Call对象。
Meta Function的调用:
// call function
template <typename... A>
static inline Value call(const Function &fn, const UserObject &obj, A&&... args)
{
return ObjectCaller(fn).call(obj,
detail::ArgsBuilder<A...>::makeArgs(std::forward<A>(args)...));
}
template <typename... A>
static inline Value callStatic(const Function &fn, A&&... args)
{
return FunctionCaller(fn).call(detail::ArgsBuilder<A...>::makeArgs(std::forward<A>(args)...));
}
// call object
class ObjectCaller
{
public:
ObjectCaller(const Function &fn);
const Function& function() const { return m_func; }
template <typename... A>
Value call(const UserObject &obj, A&&... args);
private:
const Function &m_func;
runtime::detail::FunctionCaller *m_caller;
};
class FunctionCaller
{
public:
FunctionCaller(const Function &f);
const Function& function() const { return m_func; }
template <typename... A>
Value call(A... args);
private:
const Function &m_func;
runtime::detail::FunctionCaller *m_caller;
};
可以看出对Meta Function的调用也比较简单。最终都是通过ObjectCaller&FunctionCaller调用 ,ObjectCaller针对于成员方法调用,FunctionCaller针对非成员方法调用。 Callers提供的Call的方法最终也是转发参数到FunctionCallerImp实例属性m_function上并未有对于的逻辑。最终由m_function发起实际的函数调用。
以上内容就大致介绍完成了Ponder库对于Meta Function的实现机制。也不算复杂,但逻辑很清晰。
下面看个 SampleCode:
struct TestClass
{
TestClass() {
}
~TestClass()
{
}
bool b;
int i;
std::string s;
void memberParams1() {}
void memberParams2(bool) {}
void memberParams3(float, double) {}
void memberParams4(short, int, long) {}
void memberParams5(const std::string&, std::string) {}
int funcWrapper1(int x) {return x;}
static int staticFunc()
{
return 0;
}
static float staticFunc2(float a, float b)
{
return a * b;
}
};
void declare() {
ponder::Class::declare<TestClass>().function("memberParams1", &TestClass::memberParams1)
.function("memberParams2", &TestClass::memberParams2)
.function("memberParams3", &TestClass::memberParams3)
.function("memberParams4", &TestClass::memberParams4)
.function("memberParams5", &TestClass::memberParams5)
.function("lambdaFunc1", [](TestClass& this_){ })
.function("funcWrapper1",
std::function<int (TestClass&, int)>(
std::bind(&TestClass::funcWrapper1, _1, _2)))
.function("nonClassFunc1", &TestClass::staticFunc);
}
PONDER_AUTO_TYPE(TestClass, &declare)
// 以上只是简单列出注册元函数的示例,并未包含所有的情况。
-
Meta Construct
Meta Construct 封装了类的构造信息。
ClassBuilder constructor方法提供Meta Construct 信息注册入口:
template <typename T>
template <typename... A>
ClassBuilder<T>& ClassBuilder<T>::constructor()
{
Constructor* constructor = new detail::ConstructorImpl<T, A...>();
m_target->m_constructors.push_back(Class::ConstructorPtr(constructor));
return *this;
}
方法实现也比较简单,无形参唯一需要的是一系列模板类型参数然后将模板类型参数交由ConstructorImpl类型进而实例化该对象最后将对象交由Meta Class实例管理。
constructor模板方法具体需要的类型参数包我们通过下文来分析:
Constructor 声明
class Constructor : public Type
{
public:
virtual ~Constructor() {}
virtual bool matches(const Args& args) const = 0;
virtual UserObject create(void* ptr, const Args& args) const = 0;
};
Construct为纯虚类提供注册类型实例的封装类UserObject实例的创建纯虚方法与一个匹配纯虚方法。至于匹配什么交由子类重写接口时来分析。
Constructor 子类ConstructorImpl 声明&定义
template <typename T, typename... A>
class ConstructorImpl : public Constructor
{
template <typename... As, size_t... Is>
static inline bool checkArgs(const Args& args, PONDER__SEQNS::index_sequence<Is...>)
{
return allTrue(checkArg<As>(args[Is])...);
}
template <typename... As, size_t... Is>
static inline UserObject createWithArgs(void* ptr, const Args& args, PONDER__SEQNS::index_sequence<Is...>)
{
if (ptr)
return UserObject::makeRef(new(ptr) T(convertArg<As>(args, Is)...)); // placement new
else
return UserObject::makeOwned(T(convertArg<As>(args, Is)...));
}
public:
bool matches(const Args& args) const override
{
return args.count() == sizeof...(A) && checkArgs<A...>(args, PONDER__SEQNS::make_index_sequence<sizeof...(A)>());
}
UserObject create(void* ptr, const Args& args) const override
{
return createWithArgs<A...>(ptr, args, PONDER__SEQNS::make_index_sequence<sizeof...(A)>());
}
};
通过create方法&createWithArgs方法源码可以看出ClassBuilder constructor方法的模板参数即为注册类型构造函数的形参类型列表。
而matches函数用于验证构造对象时传入的实参是否与注册Meta Construct时类型信息是否一致即调用ClassBuilder方法。
这里对Args对象未做过多说明本质上是为了解决模板技术的相关限制而用于简单封装存储模板参数包数据的。
看一个Sample Code:
struct TestType
{
TestType(int x_) : x(x_) {}
int x;
};
enum TestEnum
{
zero = 0,
one = 1,
two = 2,
three = 3,
four = 4,
five = 5
};
struct TestClass
{
MyClass()
: l(0), d(0.), s("0"), e(zero), u(0) {}
MyClass(long l_)
: l(l_), d(1.), s("1"), e(one), u(1) {}
MyClass(long l_, double r_)
: l(l_), d(r_), s("2"), e(two), u(2) {}
MyClass(long l_, double r_, std::string s_)
: l(l_), d(r_), s(s_), e(three), u(3) {}
~MyClass() {}
long l;
double d;
std::string s;
MyEnum e;
MyType u;
};
void declare() {
ponder::Class::declare<TestClass>("TestClass")
.constructor()
.constructor<long>()
.constructor<long, double>()
.constructor<long, double, std::string>()
.constructor<long, double, std::string, TestEnum>()
.constructor<unsigned short, float, std::string, TestEnum, int>();
}
PONDER_AUTO_TYPE(TestClass, &declare)
-
类型擦除对象Value Class
Value类基于Variant类型变量用来保存与类型无关的值,在泛型编程中由于我们并不知道变量的具体类型,所以Value类的实现用于统一保存模板数据很重要。 上文中的Meta Construct 使用到的Args类型即是基于Value封装的Value容器类型。 这里大致说明下Value的实现过程。
Value类的声明源码:
class PONDER_API Value
{
public:
Value();
template <typename T>
Value(const T& val);
Value(const Value& other);
Value(Value&& other);
void operator = (const Value& other);
ValueKind kind() const;
template <typename T>
T to() const;
template <typename T>
T& ref();
template <typename T>
const T& cref() const;
template <typename T>
bool isCompatible() const;
template <typename T>
typename T::result_type visit(T visitor) const;
template <typename T>
typename T::result_type visit(T visitor, const Value& other) const;
bool operator == (const Value& other) const;
bool operator != (const Value& other) const {return !(*this == other);}
bool operator < (const Value& other) const;
bool operator > (const Value& other) const
{
return !(*this < other || *this == other);
}
bool operator <= (const Value& other) const
{
return (*this < other || *this == other);
}
bool operator >= (const Value& other) const
{
return !(*this < other);
}
static const Value nothing;
private:
typedef mapbox::util::variant<
NoType, bool, long, double, ponder::String,
EnumObject, UserObject, detail::ValueRef
> Variant;
Variant m_value; // Stored value
ValueKind m_type; // Ponder type of the value
};
从Value的接口声明和属性定义中我们不难看出Value对其存储的值提供了一些简单操作接口,包括比较、类型转换、访问、value具体类型、引用类型获取等。满足了Value值的一些基本操作。
总结
标题为什么加了(1)是由于本文只是介绍了Ponder库在运行时对类反射信息的保存/注册过程的源码分析。还未介绍分析Ponder库如何在应用中使用运行时反射信息,这部分内容留到下篇(2)中去分析和分享。
到这里我们通过了Ponder源码了解到了Ponder中对于数据抽象/建模方式,设计模式的使用、泛型/元编程&C++运行时程序的最佳实践过程。笔者认为Ponder整个源码实现过程数据抽象非常清晰,实现技巧性很强,是值得学习的优秀库之一也是能帮助自己提高编码技巧与思维方式的源码教程。同时笔者希望你可以将该文加入到你的工具箱中,实际用到时可以参考源码中的相关技巧。Ponder源码中的元编程方式中用到了大量的间接层来解决问题这点同样是值的借鉴与学习。相信看完本文大家也发现了泛型&元编程在实际编程中的优势与威力,希望同大家一起交流学习同时欢迎大家指正文章错误之处。
有兴趣的小伙伴可以直接查看Ponder源码: GitHub - billyquith/ponder: C++ reflection library with Lua binding, and JSON and XML serialisation.
Thanks all for reading!!!