自动化C++程序设计---C++里的模板语言

书名: 《自动化C++程序设计》
作者: 熊春雷
网站: http://www.autodev.net
Blog: http://blog.csdn.net/pandaxcl
EMail: pandaxcl@163.com
昵称: pandaxcl,开心
QQ: 56637059
MSN: pandaxcl@163.com
版本: 0.01 于2007/09/25
目标: 所有C++爱好者
版权: 本文的版权归熊春雷所有
代码库: autocxx(在论坛下载

Warning

  1. 本文由熊春雷所写,绝对保证原创,在此特别严肃声明。本人简介:熊春 雷,男,1980年出生于湖北钟祥;七岁随父迁往宜昌开始学生生涯,小学和初中在 湖北宜昌樟村坪镇职工子弟学校就读;1996年考上宜昌县高中,开始三年的高中生 活;1999-2003就读于湖北大学物理系;2003-2006就读于武汉大学物理系。现就职 于盛大网络:)

  2. 绝对不能容忍他人说本文为他所写以及其他的侵权行为。一旦发现,一定 尽本人最大的能力以法律的形式严追到底,决不妥协。

  3. 引用本文,要保证本文的完整性,不可以删除此处的声明,并且务必注明出处。

Tip

  1. 本文编写的所有代码可以用于任何用途(包括商业用途)。

  2. 用于商业用途的需要在最后发布的软件中声明借鉴了本文的思想。具体事 宜可以协商解决,(代码决不收取任何费用)。

  3. 其他事项可以和我联系,包括技术讨论等等:)或者直接登陆网站论坛: http://www.autodev.net

Note

  1. 本文受到了《C++设计新思维》和《产生式编程》两本书的影响,同时也查阅了大 量的资料,从Loki库和Boost库中也吸收了不少营养,特此感谢之。

  2. 本文由于处于原创阶段,难免会出现各种各样的错误。代码出现错误的可能性非常 小(本来想说为零的),因为文档和代码是严格同步的,这是由VST文本的include 所保证的,代码都是测试成功之后才发布的。

  3. 本文所编写的代码,经过了VC2005编译器和g++编译器的测试,并且都通过了。

  4. 本文还没有彻底完成,算是一个初级版本,未来还将继续完善。暂时发布出来是为 了预知读者群有多少,读者越多,我的成就感越强,写作的时候也会更有动力:)

  5. 本文还会继续完善,欢迎各位读者的批评指正,也接受各种各样的建议,在权衡之 后以决定是否加入本书。

  6. 本书还没有最终完成,还会不断的进行完善,更新之后的内容将会发表于我的 网站我的博客。所以还需要读者多多关心本文的进展:)

Contents

C++里的模板语言

静态诊断

在本文中会大量的应用C++模版的 静态行为 ,也就是验证模版运算是否确实发生在编 译器编译源代码的时候。为了证明模版是运行在编译期,首先就必须给出一个可以判断是 否在编译期运行的检测机制。实际上,任何能够静态传入整型参数的编译错误都可以作为 静态断言的实现机制。

Tip

  • 编译器编译C++代码的时候,C++代码执行的运算,这就是 静态运算

  • 编译器编译完C++代码之后就产生了可执行文件,运行该可执行文件就可以执行运

  • 算,这就是 动态运算

下面看看C++中有多少种可以利用的这种编译错误:

  • 定义数组的时候,数组元素数量为零

  • 定义类对象的时候,该类有纯虚函数

首先来看看 定义数组的时候,数组元素数量为零 的情况:

 char CompileError[1==2];//在这里就会有一个编译期的错误提示  char CompileOK[1!=2];//在这里就不会有编译期的错误提示   #define STATIC_ASSERT(expr) {char CompileError[expr];}  STATIC_ASSERT(1==2);//引发编译器错误  STATIC_ASSERT(1!=2);//编译顺利通过   #define STATIC_ASSERT_MSG(expr,msg) {char CompileError##msg[expr];}  STATIC_ASSERT_MSG(1==2,_1_must_equal_2);//引发编译器错误,并给出提示  STATIC_ASSERT_MSG(1!=2,_1_must_not_equal_2);//编译顺利通过,不给出提示  

但是从上面的情况看来,还是需要利用宏来实现编译期诊断。一旦使用了宏,那么这个 STATIC_ASSERT 和STATIC_ASSERT_MSG就不能够被放到C++的名字空间(namespace)里面,这 一 点在C++中是应该尽力避免的。下面再来看看不使用宏的情况,也就是定义类对象的时 候, 该类有纯虚函数的情况:

 template<bool>struct static_assert// 这里模版参数为true是通用情况  {  };  template<>struct static_assert<false>// 针对模版参数为false进行特化  {      virtual void COMPILE_ERROR()=0;  };  

由于static_assert是模版而不是宏,因此就可以将static_assert放到C++的名字空间 (namespace)中,这样 就避免了污染全局名字空间。下面来看看这里的static_assert的使 用情况:

 static_assert<1==1>();// 不引起编译期错误  //static_assert<1==2>();// 将引起编译期错误  

有了上面的静态诊断函数之后,我们就可以利用static_assert来实现静态断言了:)但是 在书写测试代码的时候,更加常用的静态诊断是下面的两个模版:

 // 断言两个类型是否是同类型的静态诊断函数  template<class T1,class T2>struct static_assert_same// 一般情况就是一个抽象类  {      virtual void COMPILE_static_assert_same_ERROR()=0;// 纯虚函数  };  template<class T1>struct static_assert_same<T1,T1>// 针对相同类型参数进行特化  {// 没有纯虚函数,所有声明变量的时候就不会产生编译器错误  };   // 断言两个类型是否不是同类型的静态诊断函数  template<class T1,class T2>struct static_assert_not_same// 一般情况就是一个普通类  {// 没有纯虚函数,所有声明变量的时候就不会产生编译器错误  };  template<class T1>struct static_assert_not_same<T1,T1>// 针对相同类型参数进行特化  {      virtual void COMPILE_static_assert_not_same_ERROR()=0;// 纯虚函数  };  

下面就是上面的两种静态诊断的使用实例:

 static_assert_same<int,int>();  //static_assert_same<int,float>();// 将引起编译错误   static_assert_not_same<int,float>();  //static_assert_not_same<int,int>();// 将引起编译错误  

为什么说模版是C++的子语言

使用过C++的人都或多或少的使用过C++模版技术,有可能你还没有觉察到:)例如,只要是 C++的使用者 都使用过C++流,实际上比较新的C++流库都是采用的模版实现:)另外STL作为 C++标准库就是模版应用 最典型的实例之一。除此之外还有大量的C++模版库:Boost, Loki,blitz++等等。

模版作为C++的另外一个语言嵌入在C++语言里面,相对于普通的C++代码运行在运行期,模 版语言是运 行在编译期(编译器编译源代码的时期)的。

为了证明模版是C++的子语言,那么首先就必须弄明白什么是计算机语言, 不过在这里不 会进行详细的理论证明,那是计算机科学家的事情。为了简单,我在这里给出一个 计算机 语言的简单命题:

满足下列所有条件的就是计算机语言:

  • 可以执行运算(包括数值运算和符号运算)

  • 拥有选择结构

  • 拥有循环结构

常见的计算机语言,例如:C/C++ BASIC PASCAL FORTRAN 等均满足上面的三个条件。

静态数学运算

下面是最简单的模版代码(如果看不懂,可以参看介绍C++模版的书籍C++ Template 书籍 ):

 template<int a,int b>struct Add  {      enum{value=a+b};// 整数加法  };  template<int a,int b>struct Sub  {      enum{value=a-b};// 整数减法  };  template<int a,int b>struct Mul  {      enum{value=a*b};// 整数乘法  };  template<int a,int b>struct Div  {      enum{value=a/b};// 整数除法  };  template<int a,int b>struct Mod  {      enum{value=a%b};// 整数求余  };  

下面就是上面的静态加法的使用例子:

 static_assert<Add<2,1>::value == 3>();  static_assert<Sub<2,1>::value == 1>();  static_assert<Mul<2,2>::value == 4>();  static_assert<Div<4,2>::value == 2>();  static_assert<Mod<4,2>::value == 0>();  

从上面的整数运算可以看出:运算的对象仅限于整数,但是这并不是什么限制,有了整数 的 运算之后,就可以很容易的实现实数的各种运算!另外还需要注意的是:运算的结果保 存在枚举 量中。这一点和运行期运算不同,编译期的运算结果只能保存在枚举值中(我还 没有发现其它的 可以保存静态数学运算的地方:))!总结起来就是下面两条:

  • 运算的对象仅限于整数(int char short long bool)

  • 编译期的整数运算结果保存在枚举值中(value)

静态选择结构

有了上面的基本数学运算之后,再来看看静态的选择结构:

 template<int>struct on// 通用模版A  {      enum{value = -1};  };  template<>struct on<2>// 针对于2的特化版本  {      enum{value = +9};  };  template<>struct on<1>// 针对于1的特化版本  {      enum{value = +8};  };  template<>struct on<0>// 针对于0的特化版本  {      enum{value = +7};  };  

从上面的on模版可以看出,使用了C++的模版特化(这里使用的是模版完全特化)。

下面的代码就是上面的on选择结构的测试用例:

 static_assert<on<0>::value == +7>();// 选择特化模版0  static_assert<on<1>::value == +8>();// 选择特化模版1  static_assert<on<2>::value == +9>();// 选择特化模版2  static_assert<on<3>::value == -1>();// 选择通用模版A  static_assert<on<4>::value == -1>();// 选择通用模版A  static_assert<on<9>::value == -1>();// 选择通用模版A  

从上面的on模版可以看出,模版的选择结构完全依靠C++的模版(偏)特化 机制实现的。

Tip

  • 所谓的 模版完全特化模版偏特化 ,他们之间的区别体现在模版参数的

  • 特别化是否是 全部 上面:

  • 如果所有的模版参数全部被特别化了就是 模版完全特化 ,或者 模版特化

  • 只有一部分参数被特别化,则被称为者 模版部分特化模版偏特化

前面的on模版只有一个模版参数, 所以针对于2,1,0的int参数特化采用了 **完全 特化**;如果模版参数不止一个,那么就会出 现只特别化一部分参数的情况。 除了 上面的比较形式化的选择结构之外,对于整型还可以有?:选择 结构,它也是运行在编 译期的:

 static_assert<1==2?false:true>();// 不会引起编译错误  static_assert<1==1?false:true>();// 将会引起编译错误  

静态循环结构

在谈论静态循环结构之前,首先来看看计算1+2+3+...+100的数列求和问题:

 int Sum(int i)  {      if(0==i)return 0;// 递归终止条件      return i+Sum(i-1);// 进行递归处理  }   // 计算数列之和:1+2+3+...+100  assert((Sum(100)==5050));// 在程序的运行期执行  

上面的1+2+3+...+100的数列求和采用了程序设计中经常采用的递归 方法,这也是我们将 要讨论的静态C++程序设计的关键技术。

从上面递归实现的1+2+3+...+100的数列求和问题可以看出递归得以实现所必须满足的条件 :

  • 必须要有终止条件,否则就会出现无限递归

  • 递归过程可以自己调用自己,例如Sum函数体内又调用了Sum函数

有了上面的递归条件,我们再来看看静态C++代码如何通过模版来实现递归:

 template<int i>struct Sum  {      enum{value=i+Sum<i-1>::value};// 自己调用自己  };  template<>struct Sum<0>// 终止条件,这里还有一个选择结构  {      enum{value=0};  };   // 计算数列之和:1+2+3+...+100  static_assert<Sum<100>::value==5050>();// 在编译期执行  

从上面的静态循环可以看出:采用模版递归就可以实现静态循环结构。但是我们知道采用 递归方式可以实现任意形式(for while do-while)的循环结构,所以静态循环也就可以完 全实现了。

运算结果的保存

从前面的几节可以看出:我们操作的对象都是 整数 ,运算的结果也是 整数 , 但是在静态模版编程中还会大量使用 类型 作为操作对象,运算的结果也是 类型 。那么就有必要对这里的 操作对象运算结果 进行总结,同时为了使后面的 编码比较方便,必须对这里的 操作对象运算结果 进行统一!

可以想象,如果在处理的过程中只有一种 操作对象 并且也只有一种 运算结果 ,那么编码就会得到极大的简化:-)

有了这种想法之后,就存在两种方案:

  • 将类型统一到整数

  • 将整数统一到类型

很显然,第二种方案实现比较容易,因此也就出现了下面的将整数统一到类型的模版类 n

 template<int i>struct n  {      enum{value = i};// 整数值保存于此  };  

有了这里的模版类 n 之后就可以将前面的整数运算结果保存到这里的模版类 n 中:

 template<class A0,class A1>struct add// 静态整数加法  {      typedef n<A0::value+A1::value> type;  };   static_assert_same<n<3>,add<n<1>,n<2> >::type>();  static_assert_not_same<n<4>,add<n<1>,n<2> >::type>();  

可以看出,对于整数操作的对象和结果都可以象对待类型一样来操作,因此在后面的编码过程 中,都将采用这里的方法来处理整数:-)

有了上面的模板n之后,就可以将前面的静态数学运算规范化了!规范化列表如下:

 template<class A0,class A1>struct add// 静态整数加法  {      typedef n<A0::value+A1::value> type;  };   template<class A0,class A1>struct sub// 静态整数减法  {      typedef n<A0::value-A1::value> type;  };   template<class A0,class A1>struct mul// 静态整数乘法  {      typedef n<A0::value*A1::value> type;  };   template<class A0,class A1>struct div// 静态整数除法  {      typedef n<A0::value/A1::value> type;  };   template<class A0>struct inc// 静态整数増一  {      typedef n<A0::value+1> type;  };   template<class A0>struct dec// 静态整数减一  {      typedef n<A0::value-1> type;  };  

下面是上面的基本运算的测试用例:

 static_assert_same<n<5>,add<n<3>,n<2> >::type>();// 5 == 3+2  static_assert_same<n<1>,sub<n<3>,n<2> >::type>();// 1 == 3-2  static_assert_same<n<6>,mul<n<3>,n<2> >::type>();// 6 == 3*2  static_assert_same<n<1>,div<n<3>,n<2> >::type>();// 1 == 3/2  static_assert_same<n<4>,inc<n<3> >::type>();// 4 == 3+1  static_assert_same<n<2>,dec<n<3> >::type>();// 2 == 3-1  

上面的这些规范化的代码将会在后面的代码中用到,是autocxx库的组成部分:)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值