始于
分类:C++
Tags: [ C++ ]
Piccolo 源码阅读 1
C++ 反射与序列化 - 以 Piccolo 为例
反射
Meta Parser - 运行时反射的代码生成和注册
暂且不关心 parser 的代码,先看由什么生成了什么。举例 LuaComponent
的代码:
#pragma once
#include "runtime/function/framework/component/component.h"
#include "sol/sol.hpp"
namespace Piccolo
{
REFLECTION_TYPE(LuaComponent)
CLASS(LuaComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(LuaComponent)
public:
LuaComponent() = default;
void postLoadResource(std::weak_ptr<GObject> parent_object) override;
void tick(float delta_time) override;
protected:
sol::state m_lua_state;
META(Enable)
std::string m_lua_script;
};
} // namespace Piccolo
parser 生成的反射代码为:
#pragma once
#include "runtime/function/framework/component/lua/lua_component.h"
namespace Piccolo{
class LuaComponent;
namespace Reflection{
namespace TypeFieldReflectionOparator{
class TypeLuaComponentOperator{
public:
static const char* getClassName(){ return "LuaComponent";}
static void* constructorWithJson(const PJson& json_context){
LuaComponent* ret_instance= new LuaComponent;
PSerializer::read(json_context, *ret_instance);
return ret_instance;
}
static PJson writeByName(void* instance){
return PSerializer::write(*(LuaComponent*)instance);
}
// base class
static int getLuaComponentBaseClassReflectionInstanceList(ReflectionInstance* &out_list, void* instance){
int count = 1;
out_list = new ReflectionInstance[count];
for (int i=0;i<count;++i){
out_list[i] = TypeMetaDef(Piccolo::Component,static_cast<LuaComponent*>(instance));
}
return count;
}
// fields
static const char* getFieldName_m_lua_script(){ return "m_lua_script";}
static const char* getFieldTypeName_m_lua_script(){ return "std::string";}
static void set_m_lua_script(void* instance, void* field_value){ static_cast<LuaComponent*>(instance)->m_lua_script = *static_cast<std::string*>(field_value);}
static void* get_m_lua_script(void* instance){ return static_cast<void*>(&(static_cast<LuaComponent*>(instance)->m_lua_script));}
static bool isArray_m_lua_script(){ return false; }
};
}//namespace TypeFieldReflectionOparator
void TypeWrapperRegister_LuaComponent(){
FieldFunctionTuple* f_field_function_tuple_m_lua_script=new FieldFunctionTuple(
&TypeFieldReflectionOparator::TypeLuaComponentOperator::set_m_lua_script,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::get_m_lua_script,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::getClassName,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::getFieldName_m_lua_script,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::getFieldTypeName_m_lua_script,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::isArray_m_lua_script);
REGISTER_FIELD_TO_MAP("LuaComponent", f_field_function_tuple_m_lua_script);
ClassFunctionTuple* f_class_function_tuple_LuaComponent=new ClassFunctionTuple(
&TypeFieldReflectionOparator::TypeLuaComponentOperator::getLuaComponentBaseClassReflectionInstanceList,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::constructorWithJson,
&TypeFieldReflectionOparator::TypeLuaComponentOperator::writeByName);
REGISTER_BASE_CLASS_TO_MAP("LuaComponent", f_class_function_tuple_LuaComponent);
}
namespace TypeWrappersRegister{
void LuaComponent(){ TypeWrapperRegister_LuaComponent();}
}//namespace TypeWrappersRegister
}//namespace Reflection
}//namespace Piccolo
可以看到 parser 为类名生成了 getClassName
函数:
static const char* getClassName(){ return "LuaComponent";}
为 JSON 反序列化和序列化生成了 constructorWithJson
和 writeByName
函数。
为每个 META(Enable)
标记的字段生成了五个函数:
// 获取字段名
static const char* getFieldName_m_lua_script(){ return "m_lua_script";}
// 获取字段类型名
static const char* getFieldTypeName_m_lua_script(){ return "std::string";}
// 将 instance 中的该字段设置为 field_value
static void set_m_lua_script(void* instance, void* field_value){ static_cast<LuaComponent*>(instance)->m_lua_script = *static_cast<std::string*>(field_value);}
// 从 instance 中获取该字段的值
static void* get_m_lua_script(void* instance){ return static_cast<void*>(&(static_cast<LuaComponent*>(instance)->m_lua_script));}
// 是否是数组
static bool isArray_m_lua_script(){ return false; }
而后生成了注册字段信息和类信息到符号表的函数,并由 _generated/reflection/all_reflection.h
中的 TypeMetaRegister::Register
做统一调用。
将类名和上面出现的跟某一字段相关的成员函数组绑定到一个 std::multimap<std::string, FieldFunctionTuple*>
中,这算是一个全局的符号表,声明在 runtime/core/meta/reflection.cpp
中,指某个类包含这样一个字段。字段使用 FieldFunctionTuple
来描述,这是 std::tuple<SetFunction, GetFunction, GetNameFunction, GetNameFunction, GetNameFunction, GetBoolFunc>
的别名,其每一个元素代表(下标应从 0 开始但这里通过 Markdown 生成的 ol
只能从 1 开始,下同):
set_FIELD
,设置字段值,函数类型是void(void*, void*)
get_FIELD
,获取字段值,函数类型是void*(void*)
getClassName
,获取其所属的类名,函数类型是const char*()
getFieldName_FIELD
,获取字段名,函数类型是const char*()
getFieldTypeName_FIELD
,获取字段类型名,函数类型是const char*()
isArray_FIELD
,获取是否是数组,函数类型是bool()
再是类自身的绑定,将类名与自身相关的反射函数绑定到一个 static std::map<std::string, ClassFunctionTuple*>
中,这是类的一个全局符号表。ClassFunctionTuple
是 typedef std::tuple<GetBaseClassReflectionInstanceListFunc, ConstructorWithPJson, WritePJsonByName>
的别名,其中应包含:
getCLASSNAMEBaseClassReflectionInstanceList
,获取基类反射列表,函数类型是int(ReflectionInstance*, void*)
constructorWithJson
,从 JSON 的反序列化,函数类型是void*(const PJson&)
writeByName
,序列化到 JSON,函数类型是PJson(void*)
这样,由 parser 生成的所有反射的绑定均整合到了 TypeMetaRegister::Register
中,最后由 PiccoloEngine::startEngine
调用完成运行时反射。
core/meta/reflection - 运行时反射的调用接口
TypeMeta
TypeMeta
的作用是为某一个反射类提供基于 m_field_map
等全局符号表的一层抽象,用于便捷地通过 TypeMeta
访问反射类的信息。
Constructors
TypeMeta
的主构造器接收一个字符串作为参数,该字符串代表尝试访问的类名,即 type_name
。注意这个构造器是私有的,这意味着将通过其友元真正地构造一个 TypeMeta
对象,或使用下面说到的工厂函数。
这个构造器的功能很简单:为 m_field_map
中的每一个与 type_name
绑定的字段构造一个 FieldAccessor
,存入 m_fields
中。如果在 m_field_map
中没有搜索到任意字段,即表示这个 type_name
对应的反射类无字段,判该 TypeMeta
非法。
newMetaFromName
工厂函数。为非友元提供创建 TypeMeta
的方法,由参数 type_name
创建一个新的 TypeMeta
,通过公有函数访问私有构造器来绕过构造器的权限。而后返回这个将亡值。
值得注意的是,其代码为:
TypeMeta TypeMeta::newMetaFromName(std::string type_name)
{
TypeMeta f_type(type_name);
return f_type;
}
由 TypeMeta
非引用类型返回一个没有强制构造为右值引用的作为广义左值(glvalue, generic left value)的将亡值(xvalue, eXpired value),在返回时不会调用其任意(拷贝/移动)构造/赋值函数进行转移,即这是一个 zero-cost 的行为。参考如下的测试代码:
#include <iostream>
#include <string>
struct Val {
public:
Val() { m_name = "<default>"; }
Val(const std::string& name) {
std::cout << "normal construct" << std::endl;
this->m_name = name;
}
// though all these do nothing except logging
Val(const Val& rhs) {
std::cout << "copy constructor" << std::endl;
}
Val(Val&& rhs) {
std::cout << "move constructor" << std::endl;
}
Val& operator=(const Val& rhs) {
std::cout << "copy operator=" << std::endl;
return *this;
}
Val& operator=(Val&& rhs) {
std::cout << "move operator=" << std::endl;
return *this;
}
public:
static Val factory(const std::string& name) {
std::cout << "factory" << std::endl;
Val r(name);
return r;
}
private:
std::string m_name;
public:
const std::string& getName() const {
return m_name;
}
};
int main()
{
// `m_name` can be `assigned' correctly in many ways
auto v1 { Val::factory("name1") };
auto v2 ( Val::factory("name2") );
auto v3 = Val::factory("name3");
std::cout << v1.getName() << std::endl
<< v2.getName() << std::endl
<< v3.getName() << std::endl;
}
输出为:
factory
normal construct
factory
normal construct
factory
normal construct
name1
name2
name3
getFieldByName
在 m_fields
中按名搜索字段,返回其对应的 FieldAccessor
。
FieldAccessor
作用是为 FieldFunctionTuple
封装接口。
Constructors
主构造器接收一个 FieldFunctionTuple
,名为 functions
,完成下面三个行为:
- 将
functions
拷贝给成员m_functions
备用 - 立即调用 4 of
functions
,即getFieldTypeName_FIELD
,获取字段类型名并赋值给m_field_type_name
- 立即调用 3 of
functions
,即getFieldName_FIELD
,获取字段名并赋值给m_field_name
get
和 set
get
封装了 1 of m_functions
,即 get_FIELD
,获取字段值。注意这里返回的是 Any 类型(他这里用的是 C 语言的 void*,后文均使用 Any 类型代替)。
set
同理。
getOwnerTypeMeta
从 2 of m_functions
中获取字段的类名,返回由类名构建的 MetaType
。
getFieldName
、getFieldTypeName
、isArrayType
Getters。没有什么特别的。
ReflectionPtr
一个封装的反射对象,在 meta parser 生成的代码中会出现。为什么我不把它叫做指针,是因为它的用法与智能指针不完全相似,且不提供 RAII,更像是一个弱指针。
Fields
std::string m_type_name {""};
T* m_instance {nullptr};
对象名和对象值。
Assignment
ReflectionPtr
提供了多个 operator=
的 overloads,分别为:
template<typename U> ReflectionPtr<T>& operator=(ReflectionPtr<U>&& dest);
ReflectionPtr<T>& operator=(const ReflectionPtr<T>& dest);
ReflectionPtr<T>& operator=(ReflectionPtr<T>&& dest);
其中,第一个是一个函数模板,用于将一个 U
型的对象转换为一个 T
型。我推测这是为了实现运行时多态。
需要注意的是,ReflectionPtr<T>
会提供 ReflectionPtr<U>
的友元声明,否则前者无法直接访问后者的私有成员(主要是私有字段)。
第二第三个就是常规的拷贝/移动赋值。
Type Casting
非常有趣的是,这个类提供了 ReflectionPtr<T>
往 T1*
和 ReflectionPtr<T1>
转换的两组 casting operators:
template<typename T1> explicit operator T1*();
template<typename T1> operator ReflectionPtr<T1>();
template<typename T1> explicit operator const T1*() const;
template<typename T1> operator const ReflectionPtr<T1>() const;
这里因为 explicit
的限制,ReflectionPtr<T>
是不能隐式类型转换到 T1*
的。
剩下的就是一些常规的东西了。