书名: | 《自动化C++程序设计》 |
作者: | 熊春雷 |
网站: | http://www.autodev.net |
Blog: | http://blog.youkuaiyun.com/pandaxcl |
EMail: | pandaxcl@163.com |
昵称: | pandaxcl,开心 |
QQ: | 56637059 |
MSN: | pandaxcl@163.com |
版本: | 0.01 于2007/09/25 |
目标: | 所有C++爱好者 |
版权: | 本文的版权归熊春雷所有 |
代码库: | autocxx(在论坛下载) |
Warning
-
本文由熊春雷所写,绝对保证原创,在此特别严肃声明。本人简介:熊春 雷,男,1980年出生于湖北钟祥;七岁随父迁往宜昌开始学生生涯,小学和初中在 湖北宜昌樟村坪镇职工子弟学校就读;1996年考上宜昌县高中,开始三年的高中生 活;1999-2003就读于湖北大学物理系;2003-2006就读于武汉大学物理系。现就职 于盛大网络:)
-
绝对不能容忍他人说本文为他所写以及其他的侵权行为。一旦发现,一定 尽本人最大的能力以法律的形式严追到底,决不妥协。
-
引用本文,要保证本文的完整性,不可以删除此处的声明,并且务必注明出处。
Tip
-
本文编写的所有代码可以用于任何用途(包括商业用途)。
-
用于商业用途的需要在最后发布的软件中声明借鉴了本文的思想。具体事 宜可以协商解决,(代码决不收取任何费用)。
-
其他事项可以和我联系,包括技术讨论等等:)或者直接登陆网站论坛: http://www.autodev.net
Note
-
本文受到了《C++设计新思维》和《产生式编程》两本书的影响,同时也查阅了大 量的资料,从Loki库和Boost库中也吸收了不少营养,特此感谢之。
-
本文由于处于原创阶段,难免会出现各种各样的错误。代码出现错误的可能性非常 小(本来想说为零的),因为文档和代码是严格同步的,这是由VST文本的include 所保证的,代码都是测试成功之后才发布的。
-
本文所编写的代码,经过了VC2005编译器和g++编译器的测试,并且都通过了。
-
本文还没有彻底完成,算是一个初级版本,未来还将继续完善。暂时发布出来是为 了预知读者群有多少,读者越多,我的成就感越强,写作的时候也会更有动力:)
-
本文还会继续完善,欢迎各位读者的批评指正,也接受各种各样的建议,在权衡之 后以决定是否加入本书。
-
本书还没有最终完成,还会不断的进行完善,更新之后的内容将会发表于我的 网站或我的博客。所以还需要读者多多关心本文的进展:)
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库的组成部分:)