前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++反射深入浅出 - 1. ponder 反射实现分析总篇

C++反射深入浅出 - 1. ponder 反射实现分析总篇

作者头像
fangfang
发布2022-04-01 17:53:39
1.1K0
发布2022-04-01 17:53:39
举报
文章被收录于专栏:方方的杂货铺

c++反射深入浅出 - ponder 反射实现分析总篇

给静态语言添加动态特性, 似乎是C++社区一件大家乐见其成的事情, 轮子也非常多, 我们不一一列举前辈们造的各种流派的轮子了, 主要还是结合我们框架用到的C++反射实现, 结合C++的新特性, 来系统的拆解目前框架中的反射实现. 另外代码最早脱胎于Ponder, 整体处理流程基本与原版一致, 所以相关的源码可以直接参考 ponder的原始代码 . 文章计划分分7篇: - [[1. c++反射深入浅出 - ponder 反射实现分析总篇]] - [[2. c++反射深入浅出 - property实现分析]] - [[3. c++反射深入浅出 - function实现分析]] - [[4. c++反射深入浅出 - 基于反射的Lua中间层实现]] - [[5. C++反射深入浅出 - 反射信息的自动生成]] - [[6. C++反射深入浅出 - 反射的其他应用]] - [[7. C++反射深入浅出 - c++20 concept 改造]]

代码多采用c++17的特性实现, 后续考虑结合concept更好的简化相关代码, 这部分晚点再分享.

1. 简单的示例

代码语言:javascript
复制
//-------------------------------------
//register code
//-------------------------------------
using namespace framework;
using namespace framework::reflection;
using namespace framework::math;
__register_type<Vector3>("Vector3")
  .constructor()
  .constructor<Real, Real, Real>()
  .property("x", &Vector3::x)
  .property("y", &Vector3::y)
  .property("z", &Vector3::z)
  .function("Length", &Vector3::Length)
  .function("Normalise", &Vector3::Normalise)
  .overload(
      "operator*", [](Vector3* caller, Real val) { return caller->operator*(val); },
      [](Vector3* caller, const Vector3& val) { return caller->operator*(val); });


//-------------------------------------
//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);
auto obj2 = runtime::CreateWithArgs(*metaClass, Args{1.0});
ASSERT_TRUE(obj2 == UserObject::nothing);
const reflection::Property* fieldX = nullptr;
metaClass->TryGetProperty("x", fieldX);
ASSERT_TRUE(fieldX != nullptr);
const reflection::Property* fieldY = nullptr;
metaClass->TryGetProperty("y", fieldY);
ASSERT_TRUE(fieldY != nullptr);
const reflection::Property* fieldZ = nullptr;
metaClass->TryGetProperty("z", fieldZ);
ASSERT_TRUE(fieldZ != 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);
fieldX->Set(obj, 1.0);
x = fieldX->Get(obj).to<double>();
double y = fieldY->Get(obj).to<double>();
double z = fieldZ->Get(obj).to<double>();
ASSERT_DOUBLE_EQ(1.0, x);
ASSERT_DOUBLE_EQ(2.0, y);
ASSERT_DOUBLE_EQ(3.0, z);

const reflection::Function* lenfunc = nullptr;
metaClass->TryGetFunction("Length", lenfunc);
ASSERT_TRUE(lenfunc != nullptr);
const reflection::Function* normalizeFunc = nullptr;
metaClass->TryGetFunction("Normalise", normalizeFunc);
ASSERT_TRUE(normalizeFunc != nullptr);

// Overload tests
auto& tmpVec3 = obj.Ref<Vector3>();
const reflection::Function* overFunc = nullptr;
metaClass->TryGetFunction("operator*", overFunc);
Value obj3 = runtime::CallStatic(*overFunc, Args{obj, 3.0});
auto& tmpVec4 = obj3.ConstRef<UserObject>().Ref<Vector3>();
ASSERT_DOUBLE_EQ(3.0, tmpVec4.x);
ASSERT_DOUBLE_EQ(6.0, tmpVec4.y);
ASSERT_DOUBLE_EQ(9.0, tmpVec4.z);
Value obj4 = runtime::CallStatic(*overFunc, Args{obj, Vector3(2.0, 2.0, 2.0)});
auto& tmpVec5 = obj4.ConstRef<UserObject>().Ref<Vector3>();
ASSERT_DOUBLE_EQ(2.0, tmpVec5.x);
ASSERT_DOUBLE_EQ(4.0, tmpVec5.y);
ASSERT_DOUBLE_EQ(6.0, tmpVec5.z);

上面的代码演示了框架中C++属性和方法的注册, 以及怎么动态的设置获取对象的属性, 和调用相关的方法了, 最后也演示了如何调用对象的overload方法. 我们先不对具体的代码做太细致的拆解, 先有个大概印象, 注意以下几点:

  1. 对于反射对象的构建, 我们使用的代码:
代码语言:javascript
复制
runtime::createWithArgs(*metaClass, Args{ 1.0, 2.0, 3.0 });
 ASSERT_TRUE(obj != UserObject::nothing);
  1. 对于属性的获取, 我们使用的代码:
代码语言:javascript
复制
const reflection::Property* fieldX = nullptr;
    metaClass->tryProperty("x", fieldX);
    ASSERT_TRUE(fieldX != nullptr);
    Real x = fieldX->get(obj).to<Real>();
  1. 对于函数的调用 , 我们使用的代码:
代码语言:javascript
复制
const reflection::Function* overFunc = nullptr;
    metaClass->tryFunction("operator*", overFunc);
    Value obj3 = runtime::callStatic(*overFunc, Args{obj, 3.0 });

其中有几个"神奇"的类, Args, Value, UserObject, 你注意到这点, 那么恭喜你关注到了动态反射实现最核心的点了, 我们要做到运行时反射, 先得完全类型的"统一".

2. 实现概述

上图是relection的工程视图, 我们重点关注图中标有蓝色数字的几部分, 也大概对他们的作用先做简单的描述:

  1. meta: 反射信息的包装呈现的部分
  2. traits: 用于做compiler time的类型萃取, 通过相关的代码, 我们可以方便的对类的成员和函数做编译期分析, 如获取函数的返回值类型和参数类型.
  3. type_erasure: 为了让反射的使用有统一的接口, 我们必须提供基本的类型擦除容器, 从而可以运行时动态的调用某个对象的接口或者获取对象的属性.
  4. builder: meta信息不是自动就有的, 我们需要一种手段, 从原始类去构建相关的meta信息(非侵入式)
  5. runtime: 提供一些相比meta, 更友好的接口, 如上面看到的 runtime::createWithArgs() 方法.

几者的大致关系如上图所示:

  • traits, type_erasure: 提供最核心的功能支持
  • builder: 完成Compiler time对原始类的分析和Meta信息的输出, 实际上是compiler time-> runtime 的桥梁, 也相当于是整个反射的脚手架, 我们通过builder的实现来完成对反射meta信息的构成.
  • meta, runtime: 一起完成了运行时信息的表达和提供相关的使用接口.

下面我们来具体看一下每一部分的实现.

3. Meta实现 - 类C#的表达

聊到运行时系统, 我们首先想到的是弱鸡的rtti, 功能弱也就算了, 很多地方还被禁用, 能够运行时获取的信息和相关的设施都十分简陋.

另外一点要提的是, C++使用的是compiler time 类型完备的设计方式, 运行时类型都会被塌陷掉, 圈子里的黑话叫"擅长裸奔".

所以我们想要给C++增加动态反射的能力, 第一点需要做的就是想办法为它补充足够的运行时信息, 也就是类似C#的各种meta data, 然后我们就可以愉快的利用动态特性来开发一些更具通用性的序列化反序列化, 跨语言支持的功能了.

那么运行时创建操作对象, 需要哪些信息? 我们直接给出答案:

整个反射meta部分的设计其实就是为了满足前面说到的, 补齐运行时需要的类型相关的各种meta信息.

了解了meta信息包含的部分, 我们来结合代码看一下具体的实现.

3.1 reflection::Class

代码语言:javascript
复制
class Class : public Type
{
    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 meta functions indexed by name

    FunctionIdTable                 m_functions_by_id;   // Table for meta functions indexed by number id
    PropertyTable                   m_properties;            // Table of meta properties indexed by ID
    //Here use function to implement read only static properties, to get a simple implement
    FunctionTable                   m_static_properties;     // Table of meta static properties 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

    ClassUserdata                   m_userdata;
    public: // reflection
    const Class& base(size_t index) const;

    size_t constructorCount() const;
    const Constructor* constructor(size_t index) const;
    void destruct(const UserObject& uobj, bool destruct) const;

    const Function& function(size_t index) const;
    const Function& function(IdRef name) const;
    const Function* function_by_id(uint64_t funcId) const;
    bool tryFunction(const IdRef name, const Function*& funcRet) const;

    bool tryStaticProperty(const IdRef name, const Function*& funcRet) const;

    size_t propertyCount() const;
    bool hasProperty(IdRef name) const;
    const Property& property(size_t index) const;
    const Property& property(IdRef name) const;
    bool tryProperty(const IdRef name, const Property*& propRet) const;

    size_t sizeOf() const;

    UserObject getUserObjectFromPointer(void* ptr) const;

    bool has_meta_attribute(const IdRef attrName) const noexcept;
    const Value* get_meta_attribute(const IdRef attrName) const noexcept;
    std::string get_meta_attribute_as_string(const std::string_view attrName) const noexcept;
};

具体实现跟前面的图基本是一一对应的, 这里不展开细述了.

3.2 reflection::Function

代码语言:javascript
复制
class Function : public Type
{
public:
    IdReturn name() const;
    uint64_t id() 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 std::string_view paramTypeName(size_t index) const = 0;
    virtual TypeId paramTypeIndex(size_t index) const = 0;
    virtual TypeId returnTypeIndex() const = 0;

    virtual bool argsMatch(const Args& arg) const = 0;
protected:
    // FunctionImpl inherits from this and constructs.
    Function(IdRef name);

    Id m_name;                          // Name of the function
    uint64_t m_id;                      // Id 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是一个虚的实现, 后面我们会看到真正的Function是由builder来构建出来的.

另外一点是meta function没有像C#那样直接给出Invoke方法, 这个是因为目前的实现针对不同使用场合, 类型擦除的函数是不同的, 比如对于lua, 类型擦除的函数原型是 lua_CFunction. 对于C++, 则是:

代码语言:javascript
复制
std::function<Value(Args)>;

不同场合不同统一类型的好处是不需要Wrapper, 没有额外的性能开销, 但同时也会导致外围的使用变麻烦, 这里可能需要根据项目实际情况做一定的调整.

3.3 reflection::Property

代码语言:javascript
复制
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;
protected:
    virtual Value getValue(const UserObject& object) const = 0;
    virtual void setValue(const UserObject& object, const Value& value) const = 0;
protected:
    Id m_name; // Name of the property
    ValueKind m_type; // Type of the property
    TypeId m_typeIndex;

    PropertyImplementType m_implement_type = PropertyImplementType::Unknow;
};

这个类跟reflection::Function一样, 也是一个虚基类, builder会负责具体的Property的实现, 真正特化实现的主要是 getValue(), setValue() 这两个虚接口, 外部则直接使用 get(), set()接口来完成对对应属性的获取和设置操作.

3.4 static property

static property的实现比较简单, 目前其实是利用Function来间接实现的, 只提供read only 类型的static变量. 这个地方不详细展开了.

4. traits实现 - 基础工具

这部分实现也是C++新特性引入后"减法"效应比较明显的部分, 也是type erasure的基础. 核心部分主要是两块, TypeTraits和FunctionTraits, 我们主要侧重于了解两者的功能和使用, 忽略实现部分, 随着c++20的到来, concept的引入, 这部分又可以得到进一步的简化 . 目前主要是通过C++的SIFINEA特性来完成相关的推导实现, 更多是细节的处理相关的代码, 了解相关的具体实现价值感觉有限, 就不做太细致的展开了.

4.1 TypeTraits

TypeTraits主要用于移除类型的Pointer和Reference等修饰, 获取原始类型. 另外我们也可以通过其提供的get(), getPointer()函数来快速获取预期类型的数据.

代码语言:javascript
复制
template <typename T>
struct TypeTraits<T*>
{
static constexpr ReferenceKind kind = ReferenceKind::Pointer;
using Type = T*;
using ReferenceType = T*;
using PointerType = T*;
using DereferencedType = T;
using DataType = typename DataType<T>::Type;
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; }
};

4.1.1 DataType<>

TypeTraits中通过DataType<>间接获取了类型T的DataType(移除*,& 修饰的类型), DataType的实现如下:

代码语言:javascript
复制
template <typename T, typename E = void>
struct DataType
{
    using Type = T;
};

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

// smart pointer
template <template <typename> class T, typename U>
    struct DataType<T<U>, typename std::enable_if<IsSmartPointer<T<U>, U>::value >::type>
    {
        using Type = typename DataType<U>::Type;
    };

4.1.2 示例

代码语言:javascript
复制
using rstudio::reflection::detail::TypeTraits;
using rstudio::reflection::ReferenceKind;

ASSERT_TRUE(TypeTraits<int>::kind == ReferenceKind::Instance);
ASSERT_TRUE(TypeTraits<float>::kind == ReferenceKind::Instance);

ASSERT_TRUE(TypeTraits<int*>::kind == ReferenceKind::Pointer);
ASSERT_TRUE(TypeTraits<float*>::kind == ReferenceKind::Pointer);

ASSERT_TRUE(TypeTraits<float&>::kind == ReferenceKind::Reference);
ASSERT_TRUE(TypeTraits<Methods&>::kind == ReferenceKind::Reference);

ASSERT_TRUE(TypeTraits<std::shared_ptr<Methods>>::kind == ReferenceKind::SmartPointer);

4.2 FunctionTraits

FunctionTraits主要用来完成编译期对函数类型的推导, 获取它的返回值类型, 参数类型等信息. 常见于各种跨语言中间层的实现, 如Lua的各种bridge, 也是函数类型擦除的起点, 我先得知道这个函数本身的信息, 才能进一步做type erasure. 我们先来看一个例子:

代码语言:javascript
复制
/*
 * 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;
    using Details = typename function::FunctionDetails<typename std::remove_pointer<T>::type>;
    using BoundType = typename Details::FuncType;
    using ExposedType = typename Details::ReturnType;
    using ExposedTraits = TypeTraits<ExposedType>;
    static constexpr bool isWritable = std::is_lvalue_reference<ExposedType>::value
        && !std::is_const<typename ExposedTraits::DereferencedType>::value;
    using AccessType = typename function::ReturnType<typename ExposedTraits::DereferencedType, isWritable>::Type;
    using DataType = typename ExposedTraits::DataType;
    using DispatchType = typename Details::DispatchType;

    template <typename C, typename A>
    class Binding
    {
        public:
        using ClassType = C;
        using AccessType = A;

        Binding(BoundType d) : data(d) {}

        AccessType access(ClassType& c) const { return (*data)(c); }
        private:
        BoundType data;
    };
};

这个是对function和function pointer的FucntionTraits<>特化, 其他函数类型的特化提供的能力与这个的特化基本一致, 主要包含以下信息:

  1. kind: 函数的类型, 其实就是FunctionTraits的特化实现各类, 详见下文
  2. Details: 函数的具体信息, 如返回值类型, 参数表tuple<>等, 都存储在其中
  3. BoundType: 函数类型
  4. ExposedType: 返回值类型
  5. ExposedTraits: ExposedType的TypeTraits
  6. DataType: ExposedType的数据类型
  7. DispatchType: 配合std::function<>使用, 作为std::function的模板参数, 这样就可以构造一个与原始Function类型匹配的std::function<>对象了.

下文对几个重点属性做一下具体的说明:

4.2.1 FunctionTraits<>::kind

FunctionKind枚举, 主要有以下值:

代码语言:javascript
复制
/**
 * \brief Enumeration of the kinds of function recognised
 *
 * \sa Function
 */
enum class FunctionKind
{
    None,               ///< not a function
    Function,           ///< a function
    MemberFunction,     ///< function in a class or struct
    FunctionWrapper,    ///< `std::function<>`
    BindExpression,     ///< `std::bind()`
    Lambda              ///< lambda function `[](){}`
};

#### 4.2.2 FunctionTraits<>::Detatils

Details本身也是一个模板类型, 包含两种实现, FunctionDetails<> 和MethodDetails<>, 两者主要成员基本一致, MethodDetails<>针对的是类的成员函数, 所以会额外多一个ClassType, 两者差异很小, 我们就只列出FunctionDetails<>的代码了:

代码语言:javascript
复制
template <typename T>
struct FunctionDetails {};

template <typename R, typename... A>
struct FunctionDetails<R(*)(A...)>
{
    using ParamTypes = std::tuple<A...>;
    using ReturnType = R;
    using FuncType = ReturnType(*)(A...);
    using DispatchType = ReturnType(A...);
    using FunctionCallTypes = std::tuple<A...>;
};

template <typename R, typename... A>
struct FunctionDetails<R(A...)>
{
    using ParamTypes = std::tuple<A...>;
    using ReturnType = R;
    using FuncType = ReturnType(*)(A...);
    using DispatchType = ReturnType(A...);
    using FunctionCallTypes = std::tuple<A...>;
};

实现比较简洁, 主要的成员:

  1. ParamTypes: 包含函数所有参数的tuple<>
  2. ReturnType: 函数的返回值类型
  3. FuncType: 函数指针类型
  4. DispatchType: 配合std::function<>一起使用, 作为std::function的模板参数, 这样就可以构造一个与原始Function类型匹配的函数对象了.
  5. FunctionCallTypes: 同ParamTypes, 注意对于MethodDetails<>, 这两者是有区别的, ParamTypes不包含类本身, MethodDetails首个参数是类本身.

4.2.4 使用示例

代码语言:javascript
复制
using rstudio::reflection::detail::FunctionTraits;
using rstudio::reflection::PropertyKind;
using rstudio::reflection::FunctionKind;

ASSERT_TRUE(FunctionTraits<void(void)>::kind == FunctionKind::Function);

ASSERT_TRUE(FunctionTraits<void(int)>::kind == FunctionKind::Function);

ASSERT_TRUE(FunctionTraits<int(void)>::kind == FunctionKind::Function);

ASSERT_TRUE(FunctionTraits<int(char*)>::kind == FunctionKind::Function);

// non-class void(void)
ASSERT_TRUE(FunctionTraits<decltype(func)>::kind == FunctionKind::Function);

// non-class R(...)
ASSERT_TRUE(FunctionTraits<decltype(funcArgReturn)>::kind == FunctionKind::Function);

// class static R(void)
ASSERT_TRUE(FunctionTraits<decltype(&Class::staticFunc)>::kind == FunctionKind::Function);

FunctionTraits的单元测试代码, 各种类型的函数的Traits.

4.3 ArrayTraits

ponder原始的array traits主要通过ArrayMapper来完成, ArrayMapper用在了ArrayPropertyImpl中, 同其他Traits, 我们先以std::vector<>的特化实现来看一下ArrayMapper:

代码语言:javascript
复制
template <typename T>
struct ArrayMapper<std::vector<T> >
{
    static constexpr bool isArray = true;
    using ElementType = T;

    static constexpr bool dynamic()
    {
        return true;
    }

    static size_t size(const std::vector<T>& arr)
    {
        return arr.size();
    }

    static const T& get(const std::vector<T>& arr, size_t index)
    {
        return arr[index];
    }

    static void set(std::vector<T>& arr, size_t index, const T& value)
    {
        arr[index] = value;
    }

    static void insert(std::vector<T>& arr, size_t before, const T& value)
    {
        arr.insert(arr.begin() + before, value);
    }

    static void remove(std::vector<T>& arr, size_t index)
    {
        arr.erase(arr.begin() + index);
    }

    static void resize(std::vector<T>& arr, size_t totalsize)
    {
        arr.resize(totalsize);
    }

    static constexpr bool support_raw_pointer()
    {
        return true;
    }

    static void* ptr(std::vector<T>& arr)
    {
        return arr.data();
    }
};

ArrayMapper通过各种版本的特化实现提供对各类型Array进行操作的统一接口, 如:

  • size()
  • get()
  • set()
  • insert()
  • resize()等

另外对于一个类型, 我们也可以简单的通过ArrayMapper::isArray来判断它是否是一个数组.

4.4 其它Traits

4.4.1 IsSmartPointer<>

代码语言:javascript
复制
template <typename T, typename U>
struct IsSmartPointer
{
    static constexpr bool value = false;
};

template <typename T, typename U>
struct IsSmartPointer<std::unique_ptr<T>, U>
{
    static constexpr bool value = true;
};

template <typename T, typename U>
struct IsSmartPointer<std::shared_ptr<T>, U>
{
    static constexpr bool value = true;
};

判断一个类型是否为smart pointer(可以考虑直接使用concept实现).

4.4.2 get_pointer()

代码语言:javascript
复制
template<class T>
    T* get_pointer(T* p)
{
    return p;
}

template<class T>
    T* get_pointer(std::unique_ptr<T> const& p)
{
    return p.get();
}

template<class T>
    T* get_pointer(std::shared_ptr<T> const& p)
{
    return p.get();
}

为smart pointer和raw pointer提供获取raw pointer的统一接口.

5. type_erasure实现 - 统一外观

我们来回顾一下Meta部分介绍的Property的两个接口:

代码语言:javascript
复制
virtual Value getValue(const UserObject& object) const = 0;
virtual void setValue(const UserObject& object, const Value& value) const = 0;

很容易看到其中的UserObject和Value, 这基本是整个反射type_erasure实现最核心的两个对象了. 其中UserObject用于统一表达通过MetaClass创建的对象, Value则类似variants, 用于统一表达反射支持的所有类型的值. 通过这两个类型的引入, 我们很好的完成了getValue() 和 setValue()对所有类型的统一表达. 下面我们来具体介绍一下这两者的实现.

5.1 UserObject

区别于 reflection::Class 用于表达Meta信息, UserObject其实就是实际数据部分, 两者结合在一起, 我们就能够获取完整的运行时的类型表达了, 可以根据ID或者name动态的构造一个对象, 并对它进行属性的获取设置或者接口的调用.

代码语言:javascript
复制
class UserObject
{
public:
    template <typename T>
    static UserObject makeRef(T& object);

    template <typename T>
    static UserObject makeRef(T* object);

    template <typename T>
    static UserObject makeCopy(const T& object);

    template <typename T>
    static UserObject makeOwned(T&& object);

    UserObject();

    UserObject(const UserObject& other);

    UserObject(UserObject&& other) noexcept;

    template <typename T>
    UserObject(const T& object);

    template <typename T>
    UserObject(T* object);
    UserObject& operator = (const UserObject& other);
    UserObject& operator = (UserObject&& other) noexcept;

    template <typename T>
    typename detail::TypeTraits<T>::ReferenceType get() const;

    void* pointer() const;

    template <typename T>
    const T& cref() const;

    template <typename T>
    T& ref() const;

    const Class& getClass() const;

    Value get(IdRef property) const;

    Value get(size_t index) const;

    void set(IdRef property, const Value& value) const;

    void set(size_t index, const Value& value) const;

    bool operator == (const UserObject& other) const;

    bool operator != (const UserObject& other) const { return !(*this == other); }

    bool operator < (const UserObject& other) const;

    static const UserObject nothing;
private:
    void set(const Property& property, const Value& value) const;
    UserObject(const Class* cls, detail::AbstractObjectHolder* h)
        : m_class(cls)
            , m_holder(h)
        {}

    // Metaclass of the stored object
    const Class* m_class;
    // Optional abstract holder storing the object
    std::shared_ptr<detail::AbstractObjectHolder> m_holder;
};

估计跟大部分人想的满屏的复杂的实现不一样, UserObject的代码其实比较简单, 原因前面说过了, UserObject主要完成对象数据的持有和管理, 数据的持有ponder原有实现做得比较简单, 直接使用std::shared_ptr<>, 通过几种从AbstractObjectHolder继承下来的不同用途的Holder, 来完成对数据的持有和生命周期的管理.

UserObject的接口大概有几类:

5.1.1 静态构造方法

代码语言:javascript
复制
template <typename T>
static UserObject makeRef(T& object);

template <typename T>
static UserObject makeRef(T* object);

template <typename T>
static UserObject makeCopy(const T& object);

template <typename T>
static UserObject makeOwned(T&& object);

静态构造一个UserObject, 几者的主要区别在于对象生命周期管理的差异:

makeRef() : 不创建对象, 间接持有对象, 所以可能会有一个对象被UserObject持有的时候, 被外界错误的释放导致异常的问题

makeCopy(): 区别于makeRef, Holder内部会直接创建并持有一个相关对象的拷贝, 生命周期安全的实现

makeOwned(): 类同makeCopy(), 差别的地方在于会转移外界对象的控制权到UserObject, 也是生命周期安全的实现

5.1.2 泛型构造函数

代码语言:javascript
复制
template <typename T>
UserObject(const T& object);

template <typename T>
UserObject(T* object);

对类型T的构造实现, 注意内部调用的是前面介绍的makeCopy()静态方法, 所以通过这种方式构造的UserObject是生命周期安全的.

5.1.3 到原始C++类型的转换

代码语言:javascript
复制
template <typename T>
typename detail::TypeTraits<T>::ReferenceType get() const;

template <typename T>
const T& cref() const;

template <typename T>
T& ref() const;

注意转换失败会直接抛出C++异常.

5.1.4 Property 相关的便利接口

代码语言:javascript
复制
Value get(IdRef property) const;
Value get(size_t index) const;
void set(IdRef property, const Value& value) const;
void set(size_t index, const Value& value) const;

对Property进行存取操作的快捷接口.

5.1.5 其他

代码语言:javascript
复制
void* pointer() const;
const Class& getClass() const;
static const UserObject nothing;

比较常用的两个特殊接口, 其中:

pointer(): 用于获取UserObject内部存储对象的raw pointer.

getClass(): 用于获取UserObject对应的MetaClass

nothing: UserObject的空值, 判断一个UserObject是否为空可以直接与该静态变量比较

5.2 Value

Value的实现也很简单, 核心代码如下:

代码语言:javascript
复制
using Variant = std::variant<
                NoType,
                bool,
                int64_t,
                double,
                reflection::String,
                EnumObject,
                UserObject,
                ArrayObject,
                detail::BuildInValueRef
            >;
Variant m_value; // Stored value
ValueKind m_type; // Ponder type of the value

其实就是通过std::variant<>, 定义了一个包含:

  • NoType
  • bool
  • int64_t
  • double
  • string
  • EnumObject
  • UserObject
  • ArrayObject
  • BuildInValueRef 以上这些类型的一个和类型, 然后利用std::visit()访问内部的std::variant来完成各种操作的一个实现, 实现思路比较简单, 这样对于反射支持的类型, 我们都可以统一用Value来进行表达了.

常用的接口如下:

代码语言:javascript
复制
ValueKind kind() const;

template <typename T>
T to() const;

template <typename T>
typename std::enable_if_t<detail::IsUserType<T>::value, T&> ref();
template <typename T>
typename std::enable_if_t<!detail::IsUserType<T>::value, T&> ref();

template <typename T>
typename std::enable_if_t<detail::IsUserType<T>::value, const T&> cref() const;
template <typename T>
typename std::enable_if_t<!detail::IsUserType<T>::value, const T&> cref() const;
template <typename T>
bool isCompatible() const;

static const Value nothing;

6. builder实现 - 合到一起

builder的目的主要就是利用compiler time特性, 通过类型推导的方式完成从原始类型到Meta信息的生成.

6.1 function的实现

function的实现请参考[[3. c++反射深入浅出 - function实现分析]].

6.2 property的实现

property的实现请参考[[2. c++反射深入浅出 - property实现分析]].

6.3 总结

由于C++本身compiler time类型其实是相对完备的, 给我们提供了一个通过compiler time特性来完成Meta信息生成的目的, Property的实现与function类同, 利用Traits和一些其他设施, 最后整个meta class的信息就被填充完成了, 然后我们就能够在运行时对已经注册的类型进行使用了.

7. runtime实现 - 便利的运行时接口

7.1 对象创建和删除的辅助方法

代码语言:javascript
复制
template <typename... A>
static inline UserObject create(const Class& cls, A... args);

static inline UserObject createWithArgs(const Class& cls, Args&& args);

using UniquePtr = std::unique_ptr<UserObject>;
inline UniquePtr makeUniquePtr(UserObject* obj);

template <typename... A>
static inline UniquePtr createUnique(const Class& cls, A... args)

static inline void destroy(const UserObject& obj)

通过这些方法我们可以完成对反射对象的快速创建和销毁操作

7.2 函数调用

代码语言:javascript
复制
template <typename... A>
static inline Value call(const Function& fn, const UserObject& obj, A&&... args);

static inline Value callWithArgs(const Function& fn, const UserObject& obj, Args&& args);

template <typename... A>
static inline Value callStatic(const Function& fn, A&&... args);

static inline Value callStaticWithArgs(const Function& fn, Args&& args);

介绍Function对象的时候, 我们没有发现上面的invoke方法, ponder原始的实现, 是间接通过辅助的call(), callStatic()方法来完成的对应meta function的执行, 这个地方应该是可以考虑直接在Function上提供对应实现, 而不是目前的模式.

8. 反射的应用

8.1 序列化反序列化

(略)

8.2 跨语言实现

(略)

9. Future - 思考题

反射之后呢? 更进一步:

代码语言:javascript
复制
// Define the interface of something that can be drawn
struct Drawable : decltype(dyno::requires_(
  "draw"_s = dyno::method<void (std::ostream&) const>
)) { };

// Define an object that can hold anything that can be drawn.
struct drawable {
  template <typename T>
  drawable(T x) : poly_{x} { }

  void draw(std::ostream& out) const
  { poly_.virtual_("draw"_s)(out); }

private:
  dyno::poly<Drawable> poly_;
};

void f(drawable const& d) {
  d.draw(std::cout);
}

struct Square {
  void draw(std::ostream& out) const { out << "Square"; }
};

struct Circle {
  void draw(std::ostream& out) const { out << "Circle"; }
};

int main() {
  f(Square{}); // prints Square
  f(Circle{}); // prints Circle
}

需要实现类rust traits这种编译期和运行期一致的多态, 我们还需要做哪些事情?

我们已经具备的特性?

还需要的特性?

10. 小结

其实系统的了解后会发现, 随着C++本身的迭代, 像反射这种轮子, 开发难度变得越来越简单, 对比C++98年代的luabind, cpp-framework中的反射实现代码已经很精简了, 而且我们也能发现功能更强大 , 适应的场合更多了, 代码复杂度也大幅下降了, 从这个角度看, 可以看到C++新特性的迭代, 其实是在做减法的, 可以让你有更简洁易懂的方式, 去表达原来需要更复杂的实现去做的事情. 从14/17这个节点去学习和了解Meta Programming, 是一个不错的时间点.

11. 参考

  1. [github ponder库]https://github.com/billyquith/ponder
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • c++反射深入浅出 - ponder 反射实现分析总篇
  • 1. 简单的示例
  • 2. 实现概述
  • 3. Meta实现 - 类C#的表达
    • 3.1 reflection::Class
      • 3.2 reflection::Function
        • 3.3 reflection::Property
          • 3.4 static property
          • 4. traits实现 - 基础工具
            • 4.1 TypeTraits
              • 4.1.1 DataType<>
                • 4.1.2 示例
                  • 4.2 FunctionTraits
                    • 4.2.1 FunctionTraits<>::kind
                      • 4.2.4 使用示例
                        • 4.3 ArrayTraits
                          • 4.4 其它Traits
                            • 4.4.1 IsSmartPointer<>
                              • 4.4.2 get_pointer()
                              • 5. type_erasure实现 - 统一外观
                                • 5.1 UserObject
                                  • 5.1.1 静态构造方法
                                    • 5.1.2 泛型构造函数
                                      • 5.1.3 到原始C++类型的转换
                                        • 5.1.4 Property 相关的便利接口
                                          • 5.1.5 其他
                                            • 5.2 Value
                                            • 6. builder实现 - 合到一起
                                            • 6.1 function的实现
                                            • 6.2 property的实现
                                              • 6.3 总结
                                              • 7. runtime实现 - 便利的运行时接口
                                                • 7.1 对象创建和删除的辅助方法
                                                  • 7.2 函数调用
                                                  • 8. 反射的应用
                                                    • 8.1 序列化反序列化
                                                      • 8.2 跨语言实现
                                                      • 9. Future - 思考题
                                                      • 10. 小结
                                                      • 11. 参考
                                                      相关产品与服务
                                                      容器服务
                                                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                                      领券
                                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档