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

6 variant policy代码解读2

  • rttr/detail/variant/variant_data_policy.h
  • rttr/detail/variant/variant_compare.h
  • rttr/detail/variant/variant_compare.cpp
  • rttr/detail/comparison/*

剩余的policy的处理都大同小异,rttr将其中公共部分提取出来,实现了一个variant_data_policy_base类,下面的代码主要展示compare的操作。

template<typename T, typename Tp, typename Converter>
struct variant_data_base_policy
{
    static bool invoke(variant_policy_operation op, 
        const variant_data& src_data, argument_wrapper arg)
    {
        switch (op)
        {
            case variant_policy_operation::CLONE:
            {
                Tp::clone(Tp::get_value(src_data), arg.get_value<variant_data>());
                break;
            }
  
           ...

            // 这里与之前差不多,都是通过tuple然后再通过
            // argument_wrapper来传递和返回参数
            case variant_policy_operation::COMPARE_EQUAL:
            {
                const auto& param = arg.get_value<std::tuple<const variant&, 
                                    const variant&, bool&>>();
                const variant& lhs = std::get<0>(param);
                const variant& rhs = std::get<1>(param);
                bool& ok = std::get<2>(param);
                const type rhs_type = rhs.get_type();
                const type lhs_type = type::get<T>();
                const auto is_lhs_arithmetic = std::is_arithmetic<T>::value;
                
                // 这里调用compare_equal来对比类型是否一样,这里传入的类型不固定,
                // 通过偏特化模板对不同的类型采用不同的对比方法
                if (lhs_type == rhs_type)
                {
                    return compare_equal(src_data, rhs, ok);
                }
                else
                {
                    // 这里对算术数值类型做比较处理
                    if (is_lhs_arithmetic && rhs_type.is_arithmetic())
                    {
                        return variant_compare_equal(lhs, lhs_type, rhs, rhs_type, ok);
                    }
                    // 如果不是算术类型,需要尝试将rhs转成lhs的类型,并通过lhs的类型取值
                    // 再与src_data的值进行比较。
                    else
                    {
                        variant var_tmp;
                        if (rhs.convert(lhs_type, var_tmp))
                            return compare_equal(src_data, var_tmp, ok);
                        else if (lhs.convert(rhs_type, var_tmp))
                            return (var_tmp.compare_equal(rhs, ok));
                     // 如果rhs是nullptr的话,只要判断src_data是否nullptr即可
                        else if (rhs.is_nullptr())
                            return is_nullptr(Tp::get_value(src_data));
                    }
                }
                
                return false;
            }
            case variant_policy_operation::COMPARE_LESS:
            {
             
                ...
                // 这里的比较和上面COMPARE_EQUAL差不多
                if (lhs_type == rhs_type)
                {
                    // 类型相同的时候使用 compare_less_than 判断并且返回结果
                    // 如果比较失败放在后面处理
                    if ((ok = compare_less_than(src_data, rhs, result)) == true)
                        return (result == -1 ? true : false);
                }
                else
                {
                    // 类型不想同时的比较
                    return variant_compare_less(lhs, lhs_type, rhs, rhs_type, ok);
                }
                // 类型相同时如果比较失败,会尝试把lhs和rhs转换成字符串,
                // 按照字符串大小进行比较,只有字符串转换成功,
                // 并且lhs < rhs才返回true
                bool ok1 = false;
                bool ok2 = false;
                auto ret = (lhs.to_string(&ok1) < rhs.to_string(&ok2));
                if (ok1 && ok2)
                {
                    ok = true;
                    return ret;
                }
                else
                {
                    return false;
                }
            }

            ...

        }  
    }
}

一部分非公共部分的处理通过调用Tp::xxx来实现,Tp类型就是真正的variant policy,variant policy通过继承variant_data_base_policy,并且把类型通过模板传给基类,实现基类和派生类静态函数的互相调用。 比如上面的get_value,invoke函数在派生类中不会被覆盖,因此调用基类和派生类的invoke都会调用这里来,但是get_value的处理方式不一样,所以需要调用派生类的get_value来获取值。

下面对代码中compare_equal相关的函数做说明,这里主要比较lhs和rhs里实际的数据是否相等,比较相等的部分主要用了 compare_equal 和 variant_compare_equal 这两个函数,下面代码是这两个函数的实现和说明

// 判断给出的类型,返回一个bool的constexpr表示是否符合以下的类型
// 这些类型是可以进行大小表的
template<typename T>
using is_comparable_type = 
std::integral_constant<bool, std::is_same<T, std::string>::value ||                                                        
                             std::is_same<T, string_view>::value ||
                             std::is_arithmetic<T>::value ||
                             std::is_enum<T>::value ||
                             std::is_same<T, std::nullptr_t>::value ||
                             std::is_pointer<T>::value
                      >;

template<typename T>shih
using has_equal_operator_impl = 
  std::integral_constant<bool, has_equal_operator<T>::value && 
                               !std::is_array<T>::value &&                                                             
                               !is_template_instance<T>::value
                        >;

// 判断类型是否可以比较,判定的条件是是否有比较运算符,或者是否是可比较的类型
template<typename T>
using is_equal_comparable = std::integral_constant<bool,
                              has_equal_operator_impl<T>::value ||
                              is_comparable_type<T>::value>;

// 偏特化方案 1
// 如果类型可以比较,并且不是数组,返回lhs == rhs,并通过ok返回给调用者比较成功
template<typename T>
typename std::enable_if<is_equal_comparable<T>::value && 
                        !std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
    ok = true;
    return (lhs == rhs);
}

// 偏特化方案 2
// 如果类型不可比较,也不是数组,调用compare_types_equal比较
// 这个函数是从type上拿比较运算符,具体的过程在type里讲
// auto cmp_f = t.get_equal_comparator()
// return cmp_f->cmp(lhs, rhs);
// 然后调用比较运算符计算
template<typename T>
typename std::enable_if<!is_equal_comparable<T>::value && 
                        !std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
    return compare_types_equal(&lhs, &rhs, type::get<T>(), ok);
}

// 偏特化方案 3
// 如果类型不可比较并且又是数组的话
// 调用compare_array_equal进行比较
// 这个函数是会把数组分离成
// template<typename ElementType, std::size_t Count>
// struct compare_array_equal_impl<ElementType[Count]>
// 拿到元素个元素个数之后进行判断,只有lhs全部等于rhs
// 才返回true 
template<typename T>
typename std::enable_if<!is_equal_comparable<T>::value && 
                        std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
    return compare_array_equal(lhs, rhs, ok);
}

// 对于算术逻辑的处理,这里调用 variant_compare_equal 函数
// 判断方式也很简单,浮点数判定采用模糊判定
// 整形数值就直接进行判断
bool variant_compare_equal(const variant& lhs, 
                           const type& lhs_type, 
                           const variant& rhs, 
                           const type& rhs_type, 
                           bool& ok)
{
    ok = true;
    if (is_floating_point(lhs_type) || is_floating_point(rhs_type))
    {
        double p1 = lhs.to_double();
        double p2 = rhs.to_double();
        // 计算p1和p2差的绝对值,并放大100000000000倍后和lhs和rhs中小的比较
        // 如果小的话就认为相等
        return (std::abs(p1 - p2) * 100000000000 0. <= 
                std::min(std::abs(p1), std::abs(p2)));
    }
    else
    {
        return (lhs.to_int64() == rhs.to_int64());
    }
}

而比较大小的部分的代码实现和比较相等的代码实现基本是一样的, 调用variant_compare_less函数 ,函数实现的区别主要在比较过程中调用的是小于(<)运算符,这里就不在展示了。

4. variant_data_policy_small

这个policy是为了操作能够直接装入 variant_data 的数据,以下这些函数都是通过Tp:xxx(xxx就是下面的函数)的方式来调用的。

template<typename T, typename Converter>
struct variant_data_policy_small : 
    variant_data_base_policy<T, variant_data_policy_small<T>, Converter>
{template<typename T, typename Converter>
struct variant_data_policy_small : variant_data_base_policy<T, variant_data_policy_small<T>, Converter>
{
    // 对于数据足够放入variant_data的类型实行强转
    static const T& get_value(const variant_data& data)    
    {
        return reinterpret_cast<const T&>(data);
    }
    // 调用析构
    static void destroy(T& value)
    {
        value.~T();
    }
    // clone类似与拷贝构造,因为variant_data空间足够大,
    // 可以直接调用placement new在variant_data上构造新数据
    static void clone(const T& value, variant_data& dest)
    {
        new (&dest) T(value);
    }
    // 这里的交换和clone的操作一样,只是原本dest原本被认为没有值
    // 因此交换给value只要析构掉value即可
    static void swap(T& value, variant_data& dest)
    {
        new (&dest) T(value);
        destroy(value);
    }
    // 将任意类型的数据初始化给dest
    template<typename U>
    static void create(U&& value, variant_data& dest)
    {
        new (&dest) T(std::forward<U>(value));
    }
};

5. variant_data_policy_array_small

这个policy也是为了操作能够直接装入 variant_data 的数据,只不过数据比较下,比如 char[4],整个数组都是可以装入variant_data的空间的,在操作上基本和variant_data_policy_small差不多,只是在create和clone时有些区别,并且没有destroy操作。create和clone过程中需要对数组中的变量一个一个赋值,因此这里还会用到copy_array的相关模板。

// 这个模板把数组的元素类型和长度拆解出来,然后一个一个赋值
// 这里的代码做过简化,和源码不一样,方便理解
template<typename ElementType, std::size_t Count>
auto copy_array_1(const ElementType (&in)[Count], 
                ElementType (&out)[Count]) -> ElementType (&)[Count]
{
    copy_array_impl<ElementType[Count]>()(in, out);
    return out;
}

static void clone(const T& value, variant_data& dest)
{
    copy_array_1(value, const_cast<typename remove_const<T>::type&>(get_value(dest)));
}

6. variant_data_policy_arithmetic

这个policy的规则基本跟前面的一样,而且不需要销毁,只需要在create和clone的时候直接把目标变量强转成对应的type然后直接赋值就可以了,下面的代码展示create的过程,clone和create一样。

template<typename U>
static RTTR_INLINE void create(U&& value, variant_data& dest)
{
    reinterpret_cast<T&>(dest) = value;
}

7. variant_data_policy_big

variant_data_policy_big用于处理容量大于variant_data的数据,这是variant_data只能作为指针使用了。在前面确定variant_data时的typelist里面,也是包含指针的,所以variant_data的容量时肯定可以容纳下一个指针的。下面看一下它前面讲的policy有什么不一样的处理方法把。

template<typename T, typename Converter>
struct variant_data_policy_big : variant_data_base_policy<T, variant_data_policy_big<T>, Converter>
{
    // 因为variant_data是一个指针,所以通过指针来解引用取值返回
    static const T& get_value(const variant_data& data)
    {
        return *reinterpret_cast<T* const &>(data);
    }
    // 因为是指针,因此在销毁的时候需要使用delete操作
    static void destroy(T& value)
    {
        delete &value;
    }
    // clone通过new一个copy构造的对象,并强转自己为该类型指针的引用赋值指针
    // create 和 swap 操作一样,不展示
    static void clone(const T& value, variant_data& dest)
    {
        reinterpret_cast<T*&>(dest) = new T(value);
    }
};

8. variant_data_policy_array_big

variant_data_policy_big_array用于处理容量大于variant_data的数据,这是variant_data只能作为指针使用了。在前面确定variant_data时的typelist里面,也是包含指针的,所以variant_data的容量时肯定可以容纳下一个指针的。下面看一下它前面讲的policy有什么不一样的处理方法把。

// 取数据的时候有两个强转,后面一个是将无类型的data转成数组的指针
// 前面一个强转是从数组的指针中解引用并返回数组的引用
// 这样函数就能返回 T[] 的引用
static RTTR_INLINE const T& get_value(const variant_data& data)
{
    return reinterpret_cast<const T&>(*reinterpret_cast<const T*&>(data));
}

// 这里给variant_data赋值大容量数组new出来的指针,后面调用copy_array将数组元素一个个复制
template<typename U>
static RTTR_INLINE void create(U&& value, variant_data& dest)
{
    reinterpret_cast<array_dest_type&>(dest) = new T;
    copy_array(value, const_cast<typename remove_const<T>::type&>(get_value(dest)));
}

9. variant_data_policy_string

variant_data_policy_string基本继承了policy big的全部功能,只是在构造的时候字符串有char*类型和std::string类型,这两种类型在rttr里面被当成同一种类型来处理,因此在构造过程也需要同时支持2中方式。而在clone过程中,因为variant_data保存的数据是std::string的指针,不会存在有char**的形式,因此clone也就可以直接套用policy big的clone方式了。

struct RTTR_API variant_data_policy_string : variant_data_policy_big<std::string, default_type_converter<std::string>>
{
    template<typename U>
    // 这里的构造方式直接使用new std::string的方式,因为目标类型确定,
    // 构造的值必须是std::string的构造函数支持类型,然后把指针赋值给variant_data
    static RTTR_INLINE void create(U&& value, variant_data& dest)
    {
        reinterpret_cast<std::string*&>(dest) = new std::string(std::forward<U>(value));
    }
    // 这里传入的是一个char*类型,因为已知传入的类型,所以把数组的长度分离出来
    // 得到长度以后通过char数组的指针和长度就可以构造出一个std::string了
    template<std::size_t N>
    static RTTR_INLINE void create(const char (&value)[N], variant_data& dest)
    {
        reinterpret_cast<std::string*&>(dest) = new std::string(value, N - 1);
    }
};

copy_array的完整处理

之前在small array的create, clone, swap里展示的代码是简化的,主要用作说明用,这里展示完整的处理方法。简化的处理方法只能处理一维数组,而要处理多维数组的话就需要用到下面的方法。当数组通过copy_array传入copy_array_impl时,如果数组时多维的话,会在偏特化方案2里面循环递归,直至最终拿到数组元素,终结于特化方案1。

// 偏特化方案1
template<typename ElementType>
struct copy_array_impl
{
    void operator()(const ElementType &in, ElementType &out)
    {
        out = in;
    }
};

// 偏特化方案2
template<typename ElementType, std::size_t Count>
struct copy_array_impl<ElementType[Count]>
{
    void operator()(const ElementType (&in)[Count], ElementType (&out)[Count])
    {
        for(std::size_t i = 0; i < Count; ++i)
            // 如果是数组,仍然符合偏特化方案2
            copy_array_impl<ElementType>()(in[i], out[i]);
    }
};

template<typename ElementType, std::size_t Count>
auto copy_array(const ElementType (&in)[Count], ElementType (&out)[Count])
    -> ElementType (&)[Count]
{
    copy_array_impl<ElementType[Count]>()(in, out);
    return out;
}

《6 variant policy代码解读2》有1个想法

发表评论