想要学好C++的话,或是想要真正了解C++中的精髓和本质的话,C++中的STL, template ,算法都是必须要精通的。
从今天来开始学习C++中的模板,参考和摘录的书籍是《C++ Template 中文版》 和 《Boost 程序库探秘——深度解析C++标准库》(作者很喜欢笑,新技能get....)(笑)
1.基本概念
1.1 模板元编程(template meta-programing ):通常称之为元编程,是C++在发展的过程中从泛型编程衍生出的最复杂、最强大和最具威力的一种。
1.2元程序: 远程序 metaprogram , 本质上是 a program about a program , 与普通程序所不同的是, 是用来规定、描述普通程序编写方式, 一种位于普通程序之上超越普通程序的程序,是一种可以操控、产生程序的程序。并且可以这么说模板编程本质上是泛型编程的一个子集,也可以这么说在代码的编写方式中,所有使用template 的泛型代码都可以统称为元程序,因为泛型代码不是真正可以编译直接生成可执行程序的代码; 泛型代码仅仅定义了普通代码(可以生成可执行程序完成相应功能的代码)的产生规则;泛型代码是用来生成代码的“模板” 。
1.3 模板元编程与泛型编程的不同之处: 模板编程是一种“函数式编程” ,< 所谓的函数式编程是一种编程的典范,是将电脑视为函数的计算。函数编程语言中最终要的是λ 演算(lambda calculus) ,而且 演算的函数可以接受函数当做输入(参数) 和输出(返回值)也可以函数的规定。 与函数式编程相对应的是 指令式编程,和过程化编程, 与指令式编程相比较,函数式编程更加强调的是计算比指令的执行更加的重要。 而与过程化编程相比较,在函数式的编程里面, 函数的计算是可以随时调用的。>
并且这种“函数式编程”已经被证明是图灵完备的<什么是图灵完备的呢? 所谓的图灵完备的,指的就是一切可计算的问题都能够计算,这样的虚拟机或者是编程语言就叫做图灵完备的>。
1.4 模板元编程运行时期:模板元编程的执行是在编译期,模板元会将编译器变成元程序的解释器。 <什么叫做编译期? 所谓的编译期所指的就是: 利用编译程序即编译器从源语言编写的源程序中来生成目标程序的过程,后续的操作就是将目标程序通过连接的方式对其进行连接生成可执行的程序。> 也可以这么说,模板元编程产生的元程序是在编译期执行的程序,元程序所操纵的对象也不是普通的变量,因此在元程序中是不能够使用运行时的C++中的关键字的( 比如说 if ,else , for ), 可用的语法元素也是十分有限的。
1.5 模板元编程中最常用的关键字有:
enum, static ,这两个关键字是用来定义编译期的整数常量的
typedef , 这个关键字是用来定义元数据的,是元编程中最为重要的关键字
template , 这个事模板元编程的“起点” , 主要用于定义元函数;
"::" , 这个是域运算符,用于解析类型作用域获取计算结果(元数据) 。
1.6 元数据 :所谓的元数据指的就是元编程中可以操作的数据对象就是“元数据” (meta data ) , 有因为模板元编程的执行时期是在系统的编译期,所以对元数据操作的时期就是编译时期,也就是C++编译期在编译时期来对元数据进行操控的,元数据是元编程的基础。元数据都是不可变的。在模板元编程中的元数据更多的是以类型(type)的面目来出现的。 这些元数据并不是普通的运行时期的变量, 而是如 int , double . class(非模板类)这样的抽象数据类型----这是模板元编程与普通运行时期编程的最根本的一个不同点。
元数据还可以对其进行进一步的分类: 整数元数据, 值型元数据,函数元数据,类元数据(class 、struct等用户自定义类型)。
1.7 元函数 : 所谓的元函数指的是模板元编程中用于操作处理元数据的“构件”,元函数的调用是在编译时期,因为其功能和形式类似运行时的的函数而得名, 元函数是元编程中最为重要的构件。
下面是元函数中一个简单的小例子:
template <typename arg1 , typename arg2 ,...>
struct meta_function
{
typedef some-define type ;
static int const value = some-int ;
};
编写元函数就像是编写一个普通的运行时的函数,但是它的形式上确实一个模板类:
函数参数列表从普通函数的圆括号编程了对应的尖括号<> , 函数的形参变成了模板参数(即上面我们所讨论的元数据) ;因为在元编程的时候是不能够使用运行时的关键字的,所以元函数不能像普通函数那样来使用 return 来返回相应的结果,那么应该如何定义可以返回数值的模板函数呢? 可以在元函数(模板类)的内部使用typedef 来定义一个名为type 类型(type 也是元数据)或者是名为value 的数值作为返回值。
当然还需要注意的是,元函数在定义之后要通过分号来结束,因为元函数在本质上(即对于编译器来说)是C++ 中的一个类。
2. 元函数例子
#include <boost/config.hpp>
template<int N , int M >
struct meta_func1
{
BOOST_STATIC_CONSTANT(int , value = N+M) ;
};
上面的代码是一个使用boost的最一般的元函数代码,它的作用是计算两个整数的和,在这里需要注意的就是元函数 meta_func1<> 的执行过程,它的计算在编译期的时候就已经计算结束了,(在这里涉及到了模板实例化的知识),这样的话实际程序在执行的时候并没有计算动作而是直接得到接过来,就像是离线计算或是通常使用的打表的计算方法,将计算移动到了编译时期而不是运行时期来执行,这样的话就会节省运行时期所占用的时间。如果在一个大型的工程中,编译时期节约的计算的工作量就会相当可观。
同样,因为元程序是运行在编译时期的,所以下述的代码会引发错误。
int i = 10 , j = 10 ;
meta_func1<i , j >::value ;
下面的代码例子的是另一个元函数,它会返回元函数参数中的第一个元数据:
template<typename T1 , typename T2 >
struct select1st
{
typedef T1 type ;
};
返回值的规定是通过 type 来修饰的T1 来实现的。
3. 元函数的转发
模板元编程中的元函数的转发相当于运行时期的函数转发调用,但是在模板元编程过程中需要通过public 对应的派生来实现,模板参数传递给父类完成元函数的调用这样子类会获得父类的::type的定义,也就通过这种方式来完成了元函数的返回,就像是下面的例子所示,将元数据进行位置互换之后转发给之前定义的select1st<> , 就相当于下面select2nd<> 这段代码的功能:
template<typename T1 , typename T2 >
struct forward
{
typedef typename select1st<T2, T1>::type type ;
} ;