原创文章,未经许可,禁止转载!

7 type_data构造

  • rttr/type.h
  • rttr/wrapper_mapper.h
  • rttr/detail/type/type_impl.h
  • rttr/detail/type/type_data.h
  • rttr/detail/type/type_data.cpp
  • rttr/detail/impl/wrapper_mapper_impl.h

type实例的保存在第一章已经讲过,是通过模板类的单例来实现的,因此创建type实例的过程就是通过 type::get<T> 的方式去创建,既可以是主动的,也可以是被动的。

  • 被动就是在使用的时候去get一个type,如果从程序启动开始一直没有去get这个类型的type,那么rttr就会创建一个。
  • 主动就是在程序初始化的某一刻,将需要的type全部都get一遍,这样,在程序真正运行过程中就不需要去创建这些类型了。这种方式在后面注册class的时候会用到。

type实例这种单例的方式主要是要跟类型绑定,保证一个类型一个type实例,而真正存储type信息的是type类中的一个成员变量:type_data,它的定义如下:

struct RTTR_LOCAL type_data
{
    // 如果type_data是被包装过的,那么raw_type_data拿到的是原本的类型信息,
    // 并且是退化过的,如果没有被包装过拿到的是get_invalid_type_data()
    type_data* raw_type_data;      
    // 如果这个类型是被包装过的,那么wrapped_data是被包裹着的真正的类型type_data
    type_data* wrapped_type;
    // 如果是数组,array_raw_type是数组元素的type_data
    type_data* array_raw_type;
    // 类型的名称
    std::string name;
    // string_view格式的类型名称
    string_view type_name;
    // sizeof(T)
    std::size_t get_sizeof;
    // 传入的T类型有几层指针
    std::size_t get_pointer_dimension;
    // 调用create_variant函数生成variant的函数指针
    impl::create_variant_func create_variant;
    // 基类列表
    impl::get_base_types_func get_base_types;
    // 枚举包装
    enumeration_wrapper_base*  enum_wrapper;
    // 获取对象注册时的meta信息
    impl::get_metadata_func    get_metadata;
    // 按T的包装方式创建包装类型的实例
    impl::create_wrapper_func  create_wrapper;
    // 拿到注册对象的属性,函数,构造等
    impl::get_class_data_func  get_class_data;
    // 该类型是否有效
    bool is_valid;
    // 类型的标记
    type_traits m_type_traits;
};

m_type_data构造

type_data的构造在函数make_type_data里进行,通过new一个type_data,然后对type_构造赋值,并将type_data的指针包装成unique_ptr返回给type,这样唯一的type就有了唯一的type_data,部分代码如下,

template<typename T>
std::unique_ptr<type_data> make_type_data()
{
    std::unique_ptr<type_data> obj = std::unique_ptr<type_data>
    (
        new type_data
        {
            // 找到相应的type_data赋值给raw_type_data
            raw_type_info<T>::get_type().m_type_data,
            // 找到相应的type_data赋值给wrapper_type 
            wrapper_type_info<T>::get_type().m_type_data,
            // 找到相应的type_data赋值给array_raw_data
            array_raw_type<T>::get_type().m_type_data,
            
            ...
        }
    )
    return obj;
}

1. raw_type_data的初始值

raw_type_data的初值是通过raw_type_info<T>获取对应类type,从type中获取对应的type_data。raw_type_info<T>推导type的过程如下,

// 非特化
// 判断T与退化类型的T是否相同,相同则返回invalid_type
template<typename T, bool v = std::is_same<T, typename raw_type<T>::type >::value>
struct raw_type_info
{
    static type get_type() 
    {
        return get_invalid_type();
    } // we have to return an empty type, so we can stop the recursion
};
// 特化
template<typename T>
struct raw_type_info<T, false>
{
    static type get_type()
    {
        return type::get<typename raw_type<T>::type>();
    }
};

选择的依据是判断类型T与退化类型T是否一样,一样的话匹配非特化方案,返回invalid_type,因为本身T就是原始类型了,因此也没有必要在保存一个原始类型。如果不一样,则匹配特化方案,raw_type_data会返回T的退化类型的type。

这里的退化规则是:

  1. 如果是引用,则首先去除引用。
  2. 如果是指针但不是函数指针(保留函数指针),则移除指针。
  3. 最后移除const和volatile。

invalid_type的type_data是 invalid_type_data的单例,初始化时,invalid_type_data的3个type_data在创建是都被赋予nullptr,因此他本身和其他的type之间也没有什么关系,也不需要去保留其他的type_data的指针。同样,这个对象也是一个静态对象,因此也是全局唯一。

static type_data& get_invalid_type_data_impl() RTTR_NOEXCEPT
{
    static type_data instance{ 
                               nullptr, 
                               nullptr,
                               nullptr,
   ...

    return instance;
}

2. wrapped_type的初始值

wrapped_type的初值是通过wrapper_type_info<T>获取对应类type, 从type中获取对应的type_data ,推导过程如下:

// 如果是包装类型,则返回被包装的原始类型的type
template<typename T, bool = is_wrapper<T>::value>
struct wrapper_type_info
{
    static type get_type() 
    {
        return type::get<wrapper_mapper_t<T>>();
    }
};
// 如果不是包装类型,返回invalid_type
template<typename T>
struct wrapper_type_info<T, false>
{
    static type get_type()
    {
        return get_invalid_type();
    }
};

通过is_wrapper<T>判断类型T是否是被包装过的,如果是的话返回包装类型里的原始type,如果不是包装类型则返回invalid_type(),外部的wrapped_data通过获取返回type的type_data就可以知道当前类型是不是包装类型,被包装的原始类型是什么。

关于判断是否是包装类型is_wrapper<T>的推导方法如下:

struct invalid_wrapper_type { };

template<typename T>
using is_wrapper = std::integral_constant<bool, 
      !std::is_same<invalid_wrapper_type, wrapper_mapper_t<T>>::value >;

比较wrapper_mapper_t<T>推导出来的类型是不是invalid_wrapper_type,invalid_wrapper_type是一个标记的类型,没有实质用处。推导方法如下:

template<typename T>
using wrapper_mapper_t = typename wrapper_mapper<typename remove_cv<
                             typename std::remove_reference<T>::type
                             >::type>::wrapped_type;

先要将T类型的const,voilate和reference去掉,然后通过wrapper_mapper往下推,

// 非特化情况,因此认为没有包装类型,原本的T类型不需要处理
template<typename T>
struct wrapper_mapper
{
    using wrapped_type  = detail::invalid_wrapper_type;
    using type          = T;
};

// 特化情况,识别出std::shared_ptr<T>类型
template<typename T>
struct wrapper_mapper<std::shared_ptr<T>>
{
    using wrapped_type = decltype(std::shared_ptr<T>().get());	// T*
    using type = std::shared_ptr<T>;
    
    ...

};

当没有匹配到特化情况的时候,可以认为T类型是没有被包装,因此当前的类型就是T,而包装类为了让外部能识别和比较,就用了标记性的类型 invalid_wrapper_type。

如果匹配到了特化情况,这里以std::shared_ptr<T>做范例,明显可以确认外部测试的类型就是std::shared_ptr<T>,而分离出的T是被包装的原始类型,事实上,智能指针里面包裹的是一个对象的裸指针,因此,wrapped_type的类型其实应该是T*。

用同样的方法就可以判断出std::unique_ptr,std::weak_ptr,如果你觉得这些包装还不能满足你的需求,也可以自己增加一些偏特化类型来判断处理。

array_raw_type初始值

array_raw_type的初值是通过 array_raw_type <T>获取对应类type, 从type中获取对应的type_data , 推导过程如下:

// 特化方案,通过raw_array_type_t<T>获取数组元素类型,然后通过类型获取type返回
template<typename T, bool = std::is_array<T>::value>
struct array_raw_type
{
    static RTTR_INLINE type get_type() 
    {
        return type::get<raw_array_type_t<T>>();
    }
};
// 非特化方案,直接返回invalid_type
template<typename T>
struct array_raw_type<T, false>
{
    static RTTR_INLINE type get_type()
    {
        return get_invalid_type();
    }
};

通过std::is_array<T>可以很方便的判断出T类型是否是数组类型,如果是的话继续推导出数组元素的类型,如果不是的话返回invalid_type的,这样外部就可以拿invalid_type_data赋值给array_raw_type。

关于raw_array_type<T>::type推导数组元素的方法如下,

// 非特化方案,取到了元素的类型,在进一步去除约束即可
template<typename T, typename Enable = void>
struct raw_array_type
{
    using type = raw_type_t<T>;
};

template<typename T>
struct raw_array_type_impl;

template<typename T, std::size_t N>
struct raw_array_type_impl<T[N]>
{
    using type = typename raw_array_type<T>::type;
};

template <typename T>
struct raw_array_type_impl<T[]>
{
    using type = typename raw_array_type<T>::type;
};
//特化方案
template<typename T>
struct raw_array_type<T, typename std::enable_if<std::is_array<T>::value>::type>
{
    using type = typename raw_array_type_impl<remove_cv_t<T>>::type;
};

这里有两次推导过程,raw_array_type<T>的非特化部分是拿到数组元素的类型的,去除相应的约束即可拿到真正的类型返回。如果匹配到了特化方案,也就是T满足数组类型,那么会继续通过raw_array_type_impl推导,查看是否满足T[]或者T[N],如果是的话则分离出元素类型,然后在回到raw_array_type的推导,如果数组已经拆完了,那么会推导到raw_array_type的非特化版本里去,如果拆完的类型任然是数组,则循环往下推导,直至推出最终的元素type,array_raw_type就可以拿到这个类型的type data了。

至此,三个type data的赋值过程都已经完成了。

后面紧接是name和type_name的赋值,也就是这个type的字符串名称,这两个变量保存的字符串是一样的,只不过name的类型是std::string,而typename的类型是string_view,string_view是rttr实现的string类型,本质和std::string是一样的,只不过效率更高,存储空间更少。这两个字符串的值是通过调用get_type_name<T>,get_type_name<T>在通过 __FUNCSIG__(windows),__PRETTY_FUNCTION__(mac,Linux)获取模板函数签名字符串,通过字符串截断,仅保留类型部分作为type_data的字符串描述。这个字符串的描述也可以通过detail::type_register::custom_name来修改,这部分会在注册class的时候在讲到。

new type_data
{
     ...

     // name =
     ::rttr::detail::get_type_name<T>().to_string(), 
     // typename = 
     ::rttr::detail::get_type_name<T>(),

get_sizeof的赋值就直接使用sizeof(T)来获取,不过void类型和function类型,get_sizeof会直接赋值。

     // get_sizeof = 
     get_size_of<T>::value(),

get_pointer_dimension是用来维数的,也就是指针的层数,深度,计算的方法就是递归的拆解指针,拆解的类中有一个constexpr的计数,每拆一层就拿下一层的计数加1,直到解到最后一层。也就是最后一层的计数是0,他已经不是指针了,上一层指针在0上加1,就得到1层指针,以此类推,实现代码如下:

     ...

     // get_pointer_dimension =    get_pointer_dimension的赋值
     pointer_count<T>::value, 
     ...

    // 指针计数
    // 非特化,最后一层,已经不是指针了
    template<typename T, typename Enable = void>
    struct pointer_count_impl
    {
        static RTTR_CONSTEXPR_OR_CONST std::size_t size = 0;
    };
    // 特化部分,如果是指针就将下一层指针的计数加1
    template<typename T>
    struct pointer_count_impl<T, enable_if_t<std::is_pointer<T>::value &&
                                             !is_function_ptr<T>::value &&
                                             !std::is_member_pointer<T>::value>>
    {
        static std::size_t size = pointer_count_impl<remove_pointer_t<T> >::size + 1;
    };
    // 获取指针计数
    template<typename T>
    using pointer_count = std::integral_constant<std::size_t, pointer_count_impl<T>::size>;

create_variant的赋值,调用create_variant_func<T>其实就是选择一个variant policy,拿到policy后把create_variant的函数指针赋值给m_type_data的create_variant变量即可。

     ...
     // create_variant = 
     &create_variant_func<T>::create_variant,
     ...

get_base_types的赋值,是拿到type的父类的列表赋值给get_base_types,关于父类列表的东西会在class部分详细讲述。

enum_wrapper,枚举的包装,直接赋值为nullptr,这块的赋值不再这里,关于枚举的东西会在enum部分讲。

get_metadata,用于类成员变量或者成员函数说明的一个列表,在注册类的时候传入,这里是将这个获取描述列表的方法赋值给get_metadata

     ...
     // get_base_types =
     &base_classes<T>::get_types,
     // enum_wrapper = 
     nullptr,
     // get_metadata = 
     &get_metadata_func_impl<T>,
     ...

假如类型T是一个包装类型,调用get_create_wrapper_func<T>()会得到create_wrapper<Wrapper, Tp>的函数指针,这里的Wrapper是传入的包装类型,比如std::shared_ptr<T>,Tp是经过wrapper_mapper推导,就是前面wrapped_type赋初值的推导过程的type,这样Tp拿到就是包装类里的原始类型。有了这两个类型,create_wrapper在调用时可以通过Tp拿到arg的真正类型,并且通过原始类型的对象通过wrapper_mapper提供的create函数创建一个包装过的对象。比如将T类型的对象重新创建一个std::shared_ptr<T>的对象并保存在variant里。

template<typename Wrapper, typename Wrapped_Type>
void create_wrapper(const argument& arg, variant& var)
{
    if (arg.get_type() != type::get<Wrapped_Type>())
        return;

    auto& wrapped_type = arg.get_value<Wrapped_Type>();
    var = wrapper_mapper<Wrapper>::create(wrapped_type);
}

template<typename Wrapper, typename Tp = wrapper_mapper_t<Wrapper>>
enable_if_t<is_wrapper<Wrapper>::value &&
            ::rttr::detail::is_copy_constructible<Wrapper>::value &&
            std::is_default_constructible<Wrapper>::value &&
            has_create_wrapper_func<Wrapper>::value, impl::create_wrapper_func>
get_create_wrapper_func()
{
    return &create_wrapper<Wrapper, Tp>;
}

...
// create_wrapper =
get_create_wrapper_func<T>(),
...

get_create_wrapper_func<T>是返回T类型注册的所有的属性,函数,构造,基类列表,派生类信息等类信息列表的函数。

is_valid的赋值只要是普通的type都认为是有效的,true,只有invalid_type_data,也就是前面说的那个invalid_type_data的单例,它的is_valid是false。

m_type_traits是一个按位赋值的std::bitset<size_t>的类型,它的作用实在构造的时候给type的属性打标记。大部分都是用的std的模板元来给出标记,另外一小部分是为了满足需求自定义的判断。

  • is_associative_container 判断是否关联容器,比如std::map,std::unorderd_map。
  • is_sequential_container 判断是否顺序容器,比如std::vector, std::array。
  • is_template_instantiation 判断类型是否使用模板。
     ...
     // get_class_data = 
     get_create_wrapper_func<T>(),
     // is_valid = 
     true,
     // m_type_traits = 
     type_trait_value{ 
         TYPE_TRAIT_TO_BITSET_VALUE(is_class) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_enum) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_array) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_pointer) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_arithmetic) |
         TYPE_TRAIT_TO_BITSET_VALUE_2(is_function_ptr, is_function_pointer) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_member_object_pointer) |
         TYPE_TRAIT_TO_BITSET_VALUE(is_member_function_pointer) |
         TYPE_TRAIT_TO_BITSET_VALUE_2(::rttr::detail::is_associative_container, 
                                      is_associative_container) |
         TYPE_TRAIT_TO_BITSET_VALUE_2(::rttr::detail::is_sequential_container, 
                                      is_sequential_container) |
         TYPE_TRAIT_TO_BITSET_VALUE_2(::rttr::detail::template_type_trait, 
                                      is_template_instantiation)
     ...
}

发表评论