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

一、问题及解决

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

这里先从原来的模板的模板参数相关内容铺垫:

c++ 复制代码
//模板嵌套
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、模板的模板参数再嵌套

c++ 复制代码
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、注意模板参数的作用域即访问层次。模板参数的内层参数可以使用外层参数,但外层无法直接使用内层参数。这和在正常情况下类可以访问成员函数但不可以访问其内部变量类似。

二、扩展

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

c++ 复制代码
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

来查看一些模板的中间代码,但试了下,效果不是很好。也可以找其它一些工具来使用,但效果就不好说了。

其实这么做就已经有画蛇添足的味道了,但为此付出的时间可不少。

三、总结

在软件工程中,无论是设计、架构还是模块开发,甚至到具体的每一个小功能开发,整体的实现方向是简便为主。也就是说,在这些过程中,如果出现过于复杂的实现,那么极大概率是设计和思路出现了偏差。举一个简单例子,如果实现某个功能需要使用三级指针或者某个函数写了三四千行,除了极个别的特殊情况外,应该是思路出了偏差。

其实软件重构就是对原来的设计思路和实现方法的一种重新思考和再实现的过程。其中软件重构的一些思想,就是从宏观上指导重构者重构的目标和方向,这些都有更专业的书籍,此处就不再狗尾续貂,有时间,还是推荐大家认真读一下相关书籍和资料。

以本文为例,如果用到了超过两层的的模板参数,首先考虑的不是怎么完美的实现它,而是考虑是不是自己的设计和指导思路出问题;其次才是如何把问题逆向思考简化再考虑实现。如果真遇到一些特殊的情况,也需要认真的考虑是否有必要这样做,再决定是否去实现它。总之,越是复杂或者所谓精巧的实现,都意味着后期维护成本的急剧提升,绝大多数情况下是得不偿失的。

相关推荐
香菇滑稽之谈12 分钟前
责任链模式的C++实现示例
开发语言·c++·设计模式·责任链模式
蜕变的土豆18 分钟前
二、重学C++—C语言核心
c语言·c++
夏天的阳光吖2 小时前
C++蓝桥杯基础篇(十一)
开发语言·c++·蓝桥杯
Alaso_shuang2 小时前
C++多态
c++
郭涤生2 小时前
并发操作的同步_第四章_《C++并发编程实战》笔记
开发语言·c++·算法
梦醒沉醉2 小时前
C++和标准库速成(一)——HelloWorld和名称空间
开发语言·c++
BIT_Legend2 小时前
Torch 模型 model => .onnx => .trt 及利用 TensorTR 在 C++ 下的模型部署教程
c++·人工智能·python·深度学习
.ccl3 小时前
蓝桥杯省赛真题C++B组2024-握手问题
c++·算法·蓝桥杯
神里流~霜灭4 小时前
下降路径最⼩和(medium)
数据结构·c++·算法·链表·贪心算法·动态规划
tan180°4 小时前
版本控制器Git(1)
c++·git·后端