【SDU Chart Team - Core】SVG属性类设计之元组

SVG属性类设计 (2)

需求

SVG属性类设计(1)中,定义了基本SVG属性类,提供了泛型的实现方式。接下来我们需要让SVG属性能进一步适配组合型属性,例如"(a|b) (<number>)"的类型要求。(具体的例子:marker元素中的preserveAspectRatio,其合法属性为"(none|xMinYMin|xMidYMin|xMaxYMin|xMinYMid|xMidYMid|xMaxYMid|xMinYMax|xMidYMax|xMaxYMax) (meet|slice)")。由于其性质和元组一致,称之为属性元组。

属性元组的特点是包含多个不同的类型,且依次展示,并通过某个连接符连接。

设计

由于属性元组实际上是一个容器,故应当没有之前基本属性中赋值、绑定的特性。取而代之的是获取某个位置的属性。并且作为属性,其基本功能中的构造、读、提交等都应当实现。

这是最终的设计。

  • 底层容器
    _tuple是一个std::tuple容器,能够存储泛型对象;但这也为后续对象的设计带来了复杂性,因为std::tuple的泛型存储其实是在编译期通过元编程实现的,我们也必须使用元编程进行适配。

  • 基本单元
    基本单元就是容器中保存的Attribute[i],它是一些构造元组时就自动生成的对象,且顺序符合泛型列表中的顺序。

    当这些基本单元是SVG属性,元组才能生效。如果它们是SVG属性,则SVG属性与元组内部通过_getter[i]_setter[i]_attr[i]成员绑定。

  • At操作
    针对元组的容器性质的操作。返回某个索引位置的对象的引用。

  • GetCommit操作
    获取提交区的值。元组中的提交区实际上就是_attr[i],提交区值由其计算。

    这里有一个二次提交的过程:每次获取提交区的值时,需要调用_commit,把元组所维护的SVG属性Attribute[i]的未提交的值提交到_attr[i]中。

  • Commit操作
    这是元组作为SVG元素的实际提交。通过自身维护的setter提交。

细节

  1. 参数包(Parameter Pack)展开

    首先要解决的问题就是泛型容器操作的相关问题。其中一个重要问题就是如何遍历泛型容器。设计中_commit和构造都需要用到容器遍历,故优先级最高。

    • C++17

      对于参数包template <typename... Ts>,在C++17中的展开方式十分简便,只需要通过Ts...展开即可。并且:

      my_func(ts...);
      

      my_func(t1), my_func(t2), ... my_func(tn)
      

      等价。由此遍历tuple只需通过类似的原理进行。但是我们基于的是gcc-7.5.0,默认并不是支持C++17(可添加参数支持),因此需要寻找更广泛的实现方式。

    • C++11

      这是参数包被引入的正式版本,其中已提供了一种展开参数包的方式:

      template<std::size_t i, typename L>
      void _my_func() const {
          auto &u = std::get<i>(_tuple);
          _my_func_impl(u);
      }
      template<std::size_t i, typename F, typename S, typename ...R>
      void _my_func() const {
          auto &u = std::get<i>(_tuple);
          _my_func_impl(u);
          _my_func<i + 1, S, R...>();
      }
      template<typename I>
      void _my_func_impl(I &u) const {
          //...
      }
      

      这是利用元编程函数重载解析过程中的特性。对于第一个_my_func的重载,第一个模板参数表示递归深度,也就是模板泛型列表的索引值;第二个模板参数表示这是最后一个递归,仅支持一个泛型;所以递归展开到该函数就会结束。对于第二个_my_func的重载,第一个模板参数和前面一致;第二个模板参数是当前泛型;最后一个模板参数是参数包;函数中最后一行进行递归;所以如果参数包中有多于一个模板参数时,会一直匹配到该函数,然后进行展开,直到仅剩一个模板参数。最后一个函数是具体实现,在上述例子中表示对元组_tuple中取得的某个对象进行操作。

      对于合法类型的检验,则使用和之前类似的方法,通过成员模板重载实现。

  2. 模板对象的声明与定义

    如果将模板对象的声明和定义分别写入一个.h头文件和一个.cpp实现文件中,则编译会一直报错:找不到声明。

    实际上在前面实现模板成员函数时,也遇到过类似问题,解决方法是将.cpp文件直接#include到.h文件最后。但是模板对象仿佛并不能通过这样的方式解决,因为编译器找不到具体的声明。

    解决方法:将.h和.cpp合并为.hpp,边声明边定义。

  3. 模板成员函数作为std::function

    一开始是通过模板成员函数的方式表示_getter[i]_setter[i],但后续无论如何都无法被std::function作为值接收。其间尝试过std::bind与对象绑定、static_cast解决模板函数的重载问题,但始终提示unresolved overloaded function type

    解决方法:使用lambda表达式,能够无视上述情况直接传递。

  4. 引用问题

    在构造函数中,首先是发现_getter[i]_setter[i]无法正确传递,而后发现两对象的指针不一致,于是发现:

    auto u = std::get<i>(_tuple);
    

    左值是拷贝。因此u和原对象不同。

    解决方法:改为auto &u

  5. 只读问题

    _commitget_commit调用,因此也是只读的。但是随后在进行了(4)中的修改后发现递归中出现无法与重载匹配的情况。

    细察得知std::get在const函数中获取的是const T&

    解决方法:将_commmit中的auto &u改回auto u。这样虽是获取对象浅拷贝,但结果保证正确,因为SVG属性中的_commit仍然指向原对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值