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

3 variant构造

  • rttr/variant.h
  • rttr/variant.cpp
  • rttr/detail/variant/variant_impl.h

rttr的variant主要的功能将对象类型擦除后用于统一传递和保存,同时在variant构造的时候会同时保存传入对象的type信息,方便以后恢复。

variant的构造

先看variant一个最重要的构造函数。

template<typename T, typename Tp = detail::decay_variant_t<T>>
variant(T&& val) : m_policy(&detail::variant_policy<Tp>::invoke)
{
    detail::variant_policy<Tp>::create(std::forward(val), m_data);
} 

注意:列出的代码做出一部分简化,请以源作者代码为准

构造函数有一个模板实参,支持任意类型,本质上就是要把任意类型的对象包裹到variant里去。模板的第二个类型 Tp = detail::decay_variant_t<T>> 是将T类型做一个退化,这里的退化过程不是使用的std::decay,不同于std::decay的地方在于这里的退化不对数组进行退化,简而言之就是不会把 T[] 退化成 T*。

  • m_policy的初值

m_policy的值是根据类型T推导出对应的variant_policy的invoke函数指针,因为variant是将变量的类型擦除后保存的,因此变量的一些必要的操作就要通过variant_policy去执行,而m_policy赋值就是将对应policy的静态成员函数invoke的函数指针赋给它,之后就可以通过调用m_policy,并传递操作类型就可以操作variant里包裹的数据了。下面是部分关于数值类型variant_policy的处理:

template <typename T>
struct RTTR_API variant_data_policy_arithmetic
{
    static bool invoke(variant_policy_operation op, 
      const variant_data& src_data, argument_wrapper arg)
    {
        switch (op)
        {
            case variant_policy_operation::GET_VALUE:
            {
                arg.get_value<const void*>() = &reinterpret_cast<const T&>(src_data);
                break;
            }
...  // 为了方便理解,代码有修改

这里的invoke只展示了GET_VALUE的操作,arg.get_value在上一章就讲过了,等号前面是将arg.m_data强制转成const void*,用来存放等候后面src_data强制转换成T的地址,这样variant的值就可以通过arg传递到函数外面了,但是生存期仍然由variant控制。

  • m_data的初值

m_data 类型是variant_data,定义如下:

using variant_basic_types = type_list<bool,
                                      signed char, unsigned char, char, wchar_t,
                                      short int, unsigned short int, int, unsigned int,
                                      long int, unsigned long int, long long int,
                                      unsigned long long int, float, double, void*>;

using variant_data = std::aligned_storage<max_sizeof_list<variant_basic_types>::value,
                     max_alignof_list<variant_basic_types>::value>::type;

variant_basic_types是一个类型的列表,通过max_sizeof_listmax_alignof_list找到这个列表中大小(sizeof)和对齐大小(alignof)最大的两个值,通过std::aligned_storage定义这样的一个空间。因此,m_data所分配到的空间是可以放下以上所有类型中的任意一个,类似于union,并且是对齐的。

下面来看一下max_sizeof_list的实现,

template <typename T, typename...Ts>
struct max_sizeof_list_impl;

template <typename T>
struct max_sizeof_list_impl<T>
{
    static size_t value = sizeof(T);
};

template<typename T1, typename T2, typename... U>
struct max_sizeof_list_impl<T1, T2, U...>
{
    static std::size_t value =
        max_sizeof_list_impl<std::condition_t<sizeof(T1) >= 
        sizeof(T2), T1, T2>, U...>::value;
};

template<typename... Ts>
using max_sizeof_list = std::integral_constant<std::size_t,
                       max_sizeof_list_impl<Ts...>::value>;

max_sizeof_list是一个constexpr的值,通过递归的推导 max_sizeof_list_impl<T1, T2, U…>,最终得到唯一的一个类型 ,最后通过 max_sizeof_list_impl<T> 得出最终的sizeof(T)的值,也就是这些类型中最大的一个。

max_alignof_list 的值的获取方法也是一样,只不过比较过程中用的不是 sizeof,而是 std::alignment_of,同样的方法就可以得到最大的对齐值。

发表评论