跟我学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

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

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

三、总结

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

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

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

相关推荐
湖南罗泽南20 分钟前
Windows C++ TCP/IP 两台电脑上互相传输字符串数据
c++·windows·tcp/ip
可均可可1 小时前
C++之OpenCV入门到提高005:005 图像操作
c++·图像处理·opencv·图像操作
zyx没烦恼1 小时前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
机器视觉知识推荐、就业指导1 小时前
基于Qt/C++与OpenCV库 实现基于海康相机的图像采集和显示系统(工程源码可联系博主索要)
c++·qt·opencv
myloveasuka2 小时前
类与对象(1)
开发语言·c++
ROC_bird..3 小时前
STL - vector的使用和模拟实现
开发语言·c++
机器视觉知识推荐、就业指导3 小时前
C++中的栈(Stack)和堆(Heap)
c++
Mr_Xuhhh5 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
无敌岩雀5 小时前
C++设计模式行为模式———命令模式
c++·设计模式·命令模式
爱吃生蚝的于勒7 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法