跟我学c++高级篇——多重模板的扩展

224 篇文章 93 订阅
文章探讨了在开发过程中如何避免过度使用复杂模板,强调在设计和实现时应追求简便,减少后期维护成本。作者通过实例展示了模板嵌套的运用,并指出三层以上模板可能是设计思路偏差的标志,提倡重构和简化设计。
摘要由CSDN通过智能技术生成

一、问题及解决

在开发的过程中遇到一个问题,就是用来实现一个多数据联合存储的问题。考虑到后期的简单扩展,考虑使用了双重模板,当时一时兴起,甚至想如果用三重模板怎么样?搞了半天,发现使用那个没有啥意义,徒然引入了更多的问题。
这里先从原来的模板的模板参数相关内容铺垫:

//模板嵌套
template <typename T> struct A {};
template <typename T> struct B {};

// function nest
template <template <typename T> class MyData> constexpr auto Test0(MyData<int> md) { return 0; }
template <typename N, template <typename T> class MyData> constexpr auto Test(MyData<N> md) { return 0; }

// template template parmsater
auto c = B<A<int>>();

template <typename T1, typename T2> struct BB {};
template <typename T, template <typename> typename N> struct AB {
  AB() { std::cout << "AB is construct!" << std::endl; }
};
template <typename T, template <typename T1> typename N> struct AB1 {
  AB1() { std::cout << "AB1 is construct!" << std::endl; }
};
template <typename T, typename P, template <typename T1, typename T2> typename N> struct ABC {
  ABC() { std::cout << "ABC is construct!" << std::endl; }
};
int main() {
  AB<int, B> ab;
  AB1<int, A> ab1;
  ABC<int, double, BB> abc;

  Test0(A<int>{});
  Test(B<A<int>>{});
  return 0;
}

上述的代码从简单的基础模板到初步的嵌套模板到函数模板的模板参数到类模板的模板参数,这算是一个基础的应用,同时,也对原来模板参数中“auto c = B<A>()”进行完善说明。
下面正式引入问题:
1、模板的模板参数应用
2、模板的模板参数再嵌套

template <typename T, class con = std::vector<T>> struct DataVec {
  con vec;
  void add(T t) {
    vec.emplace_back(t);
    std::cout << "cur vec index 0:" << vec[0] << std::endl;
  }
};

template <typename T, template <typename Elem> class con> class DataList1 {
public:
  con<T> list;
  void add(T t) {
    list.emplace_back(t);
    std::cout << "cur vec index 0:" << list[0] << std::endl;
  }
};
template <typename T, template <typename Elem> class con = std::list> class DataList {
public:
  con<T> list;
  void add(T t) {
    list.emplace_back(t);
    std::cout << "cur vec index 0:" << list[0] << std::endl;
  }
};
template <typename T, template <typename Elem> class con = std::list> class DataListQueue {
public:
  con<T> list;
  void add(T t) {
    list.add(t);
    // std::cout << "cur vec index 0:" << list[0] << std::endl;
  }
};
template <typename T, template <typename Elem> class con = std::list> class DataListVec {
public:
  con<T> list;
  void add(T t) {
    list.add(t);
    // std::cout << "cur vec index 0:" << list[0] << std::endl;
  }
};

// ERROR before C++17,must under comment code
// template <typename T,
//         template <typename ELEM,
//                  typename ALLOC = std::allocator<ELEM> >
//         class CONT = std::deque>
template <typename T, template <typename> typename con = std::queue> class DataQueue {
public:
  con<T> queue;
  void add(T t) {
    queue.emplace_back(t);
    std::cout << "cur vec index 0:" << queue[0] << std::endl;
    //    std::cout << "cur vec index 0:"
    //              << "queue[0] " << std::endl;
  }
};
template <typename T, template <typename> typename con = std::queue> class DataQueue1 {
public:
  con<T> queue;
  void add(T t) {
    queue.emplace(t);
    std::cout << "cur vec index 0:" << queue.front() << std::endl;
    //     std::cout << "cur vec index 0:"
    //               << "queue[0] " << std::endl;
  }
};
int main() {

  DataVec<int, std::vector<int>> ddv;
  DataQueue<int, std::vector> ddqv;
  ddqv.add(101);

  DataList1<int, std::vector> ddlv;
  ddlv.add(111);

  DataVec<int> dv;
  dv.add(10);

  DataListVec<int, DataVec> dldv;
  dldv.add(321);

  DataListQueue<int, DataQueue1> dldq;
  dldq.add(1110);

  return 0;
}

这个代码和相关资料中使用模板的模板参数和侯捷老师认为不是模板的模板参数的例子相似,但是解决了使用此类模板进行了再嵌套(类似于Stack中再使用类似Stack的方式)。后来考虑,其实这个意义不大,但在某些情况下可能会有用。
这里面需要注意的两个问题:
1、在实现类似Stack的类模板中,在早期标准里需要实现默认分配(Alloc),但在新标准(C++17,但本机测试C++11也没问题)不需要处理。
2、注意使用确定模板类型做为参数和模板的模板参数的不同即类似DataVec这种情况。
3、模板的模板参数内层的参数如果不使用可以省略名称,只保留typename即可。另外,模板的模板参数第二个参数在C++17前只能使用class,C++11后虽然可以使用别名,但真正可以使用typename,只有在C++17的标准后。
4、注意模板参数的作用域即访问层次。模板参数的内层参数可以使用外层参数,但外层无法直接使用内层参数。这和在正常情况下类可以访问成员函数但不可以访问其内部变量类似。

二、扩展

在上面的基础上,引入三重模板:

template <typename T, template <typename> class con = std::vector> struct DataCon {
  con<T> c;
  void add(T t) {}
};

template <typename T, template <typename N, template <typename> class scon = std::vector> typename con>
struct DataCon3 {

  con<T> c;
  con<T, std::vector> c1;

  void add(T t) { /*c.c.emplace_back(t);*/
    c1.c.emplace_back(t);
  }
  void c2add(T t) { c.add(t); }
};
int main()
{
  DataCon3<int, DataCon> dc3;
  dc3.add(210);

  dc3.c2add(10);

  return 0;
}

其实,在这个问题解决的并不完美,应该增加一下SFINAE或者Concepts的处理和判断,这样会使这个模板类的应用更安全,但考虑到此处只是一个解决问题的例程,就不再继续完善下去,有兴趣的小伙伴可以自己试试。
在编译的过程中可以使用一些工具如:

clang++ -std=c++11 -Xclang -ast-print -fsyntax-only main.cpp

来查看一些模板的中间代码,但试了下,效果不是很好。也可以找其它一些工具来使用,但效果就不好说了。
其实这么做就已经有画蛇添足的味道了,但为此付出的时间可不少。

三、总结

在软件工程中,无论是设计、架构还是模块开发,甚至到具体的每一个小功能开发,整体的实现方向是简便为主。也就是说,在这些过程中,如果出现过于复杂的实现,那么极大概率是设计和思路出现了偏差。举一个简单例子,如果实现某个功能需要使用三级指针或者某个函数写了三四千行,除了极个别的特殊情况外,应该是思路出了偏差。
其实软件重构就是对原来的设计思路和实现方法的一种重新思考和再实现的过程。其中软件重构的一些思想,就是从宏观上指导重构者重构的目标和方向,这些都有更专业的书籍,此处就不再狗尾续貂,有时间,还是推荐大家认真读一下相关书籍和资料。
以本文为例,如果用到了超过两层的的模板参数,首先考虑的不是怎么完美的实现它,而是考虑是不是自己的设计和指导思路出问题;其次才是如何把问题逆向思考简化再考虑实现。如果真遇到一些特殊的情况,也需要认真的考虑是否有必要这样做,再决定是否去实现它。总之,越是复杂或者所谓精巧的实现,都意味着后期维护成本的急剧提升,绝大多数情况下是得不偿失的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值