《C++模板元编程技术与应用.ppt》由会员分享,可在线阅读,更多相关《C++模板元编程技术与应用.ppt(52页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、C+模板元编程技术与应用荣耀动机让更多的C+程序员了解模板元编程,并在此过程中获得快乐!目录*历史*导入范例*主要思想*静态语言设施*控制结构*数据结构*数值计算*类型计算*代码生成*断言和契约*库*DSEL设计*结语*资源历史1994年,在圣迭哥举行的一次C+标准委员会会议期间,Erwin Unruh展示了一段特别的代码,可以在编译期以编译错误信息的方式产生从2到某个给定值之间的所有质数。这份代码的原始版本见注5,修订版见注6。可以使用GCC编译器观察到上述效果。同年夏天,Todd Veldhuizen受Erwin的例子启发,发现可以使用C+模板进行元编程(metaprogramming),
2、并发表了一份技术报告。次年5月又在C+Report上发表了一篇名为“Using C+template metaprograms”的文章,从而将Erwin Unruh发现的C+编译期模板编程(Compile-time Template Programming)进一步精化为C+模板元编程(Template Metaprogramming,TMP)。导入范例/主模板templatestruct Fib enum Result=Fib:Result+Fib:Result;/完全特化版template struct Fib enum Result=1;/完全特化版template struct Fib
3、enum Result=0;1.计算Fibonacci数列第N项/示例int main()int i=Fib:Result;/std:cout i std:endl;主模板用于处理一般的逻辑处理N=1的情况处理N=0的情况特化必须放在主模板之后导入范例语句int i=Fib:Result;被VC7.1编译成如下指令(Intel P4 CPU):00411A1E mov dword ptr i,37h 字面量37h即为Fibonacci数列的第10项的值。可见,Fib:Result的确被评估于编译期,结果作为处理器指令的一部分而被存储起来。运作机理:当编译器实例化Fib时,为了给其enum Re
4、sult赋值,编译器需要对Fib和Fib进行实例化,同理,又需要针对Fib和Fib实例化同样的模板,当实例化到Fib和Fib的时候,完全特化版被实例化,至此递归结束。这个处理过程类似于递归函数调用,编译器被用于解释元程序,生成的结果程序中仅包含一个常量值。导入范例/主模板templatestruct IfThenElse typedef T1 ResultType;/局部特化templatestruct IfThenElse typedef T2 ResultType;/示例IfThenElse:ResultType i;/i的类型为int2.类型选择基于给定的布尔常量表达式在两个类型之中二选
5、一。若表达式为true,则T1被typedef为ResultT,否则ResultT代表T2。只针对模板参数的局部进行特化。主要思想利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。静态语言设施模板元编程使用静态C+语言成分,编程风格类似于函数式编程,其中不可以使用变量、赋值语句和迭代结构等。在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型。被操纵的实体也称为元数据(Metadata)。所有元数据均可作为模板参数。其他元数据类型还包括枚举、函数指针/引用、全局对象的指针/引用以及指向成员的指针等。另外,已经有一
6、些编译期浮点数计算探索(参见注7)。由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。静态语言设施编译期赋值通过整型常量初始化和typedef语句实现。例如:enum Result=Fib:Result+Fib:Result;或static const int Result=Fib:Result+Fib:Result;成员类型则通过typedef引入,例如:typedef T1 Result;新、旧编译器均支持静态语言设施条件结构采用模板
7、特化或条件操作符实现。如果需要从两个或更多种类型中选其一,可以使用模板特化,如前述的IfThenElse。可以使用条件操作符返回两个候选数值之一,例如:templatestruct Max enum Result=(a b)?a:b;静态C+代码使用递归而不是循环语句。递归的终结采用模板特化实现。如果没有充当终结条件的特化版,编译器将一直实例化下去,一直到达编译器的极限。C+标准建议编译器实现至少要支持17层实例化。大多数编译器支持的递归实例化数目远不止17。例如,GCC支持多达500层递归模板实例化。C+静态语言设施是图灵完备的,理论上可以用于实现任何可实现的算法。可以使用模板元编程实现与运
8、行期C+所对应的程序流程控制结构。控制结构/If/主模板templatestruct If;/完全特化版templatestruct If static void F()/待执行的语句 ;/完全特化版templatestruct If static void F()/待执行的语句 ;/示例If:F();主模板未必一定要予以定义。此主模板纯粹供随后的特化所用。控制结构/For/主模板templatestruct For static inline void f()/待执行的语句 For:f();/完全特化版templatestruct For static inline void f()/空,什
9、么都不做 ;类似地,可以给出While、Do-While实现/使用For:f();我们必须给出这个什么也不做的f(),否则会导致N=1时实例化失败。控制结构/Switch/主模板templatestruct Switch static void f()/缺省情况下执行的语句 ;/完全特化版1templatestruct Switch static void f()/待执行的语句1 ;/完全特化版2templatestruct Switch static void f()/待执行的语句2 ;/示例Switch:f();该实现不够直观,在语法上和运行期switch相去甚远。一个更自然的实现应该支持
10、类似于运行期switch的语法控制结构struct A static void execute()cout A endl;struct B static void execute()cout B endl;struct C static void execute()cout Default endl;/用法示例Switch(1+1-2),Case1,A,Case2,B,Case :Result:execute();/打印“Default我们希望支持这样的用法控制结构/Switchconst int DEFAULT=-1;struct NilCase;template struct Case e
11、num tag=tag_;typedef Type_ Type;typedef Next_ Next;/主模板template struct Switchprivate:typedef typename Case:Next NextCase;enum caseTag=Case:tag,found=(caseTag=tag|caseTag=DEFAULT);public:typedef typename IfThenElsefound,typename Case:Type,typename Switch:Result:ResultType Result;/局部特化template struct
12、Switch typedef NilCase Result;利用typename消除歧义 如前述 类似地,可以分别给出更符合直觉的、结构性更好的For、While、Do-While实现。控制结构最早提出编译期控制思想并给出雏形实现的是Todd Veldhuizen,参见注1。Krysztof Czarnecki,Ulrich Eisenecker则实现了更一般化的编译期结构(如刚才展示的第二个版本的Switch)参见注12。可以使用嵌套模板实现复杂的编译期数据结构(编译期容器),其中可以容纳整数和类型。考察两个例子:一个序列,为Loki库中的Typelist;一个是二叉树(或树的二叉树表示)结
13、构。数据结构Boost MPL库中定义有vector、deque、list、set以及map等序列,它们都是编译期数据结构。数据结构/Typelisttemplate struct Typelist typedef T Head;typedef U Tail;/计算长度/主模板template struct Length;/局部特化template struct LengthTypelist enum value=1+Length:value;/完全特化template struct Length enum value=0;/示例typedef Typelistchar,Typelist T;
14、cout Length:value endl;/2/哨卫类型class NullType;一 个 typelist的 长 度 等 于tail的长度加1数据结构/BTreeconst int LEAFVALUE=-1;/叶子节点值/叶子节点struct BTLeaf enum Value=LEAFVALUE;typedef BTLeaf Left;typedef BTLeaf Right;template struct BTree/BTNode enum Value=N;typedef Left_ Left;typedef Right_ Right;数据结构/判树是否为空/主模板template
15、 struct IsEmpty;/局部特化template struct IsEmptyBTree enum Result=false;/完全特化templatestruct IsEmptyBTree enum Result=true;数据结构/示例typedef BTree tree1;typedef BTree tree2;typedef BTree1,BTree,BTree tree3;int main()cout IsEmpty:Result endl;/1 cout IsEmpty:Result endl;/0 cout IsEmpty:Result endl;/0等价于:typed
16、ef BTree aTree2;由于模板元编程最先是因为数值计算而被发现的,因此早期的研究工作主要集中于数值计算方面,先锋是Todd Veldhuizen和Blitz+库。元编程在该领域最早的应用是实现“循环开解(Unroll Loop)”。其他库(例如MTL、POOMA等)也采用了这种技术。数值计算数值计算领域还有很多重要的模板元编程(相关)技术,例如“表达式模板(Expression Templates)”(见注2)、“部分求值(Partial Evaluation)”(见注3)等。数值计算/主模板template struct DotProduct static T Result(T*a
17、,T*b)return*a*b +DotProduct:Result(a+1,b+1);/局部特化template struct DotProduct static T Result(T*a,T*b)return*a*b;一个经典的循环开解例子:计算向量点积算法思想:向量a和b的点积=向量a和b首元素的乘积+剩余维度向量之点积一维向量的情形数值计算/示例int main()int a5=1,2,3,4,5;int b5=6,7,8,9,10;cout DotProduct:Result(a,b)endl;/130/循环开解过程DotProduct:result(a,b)=*a*b +DotPr
18、oduct:result(a+1,b+1)=*a*b +*(a+1)*(b+1)+DotProduct:result(a+2,b+2)=*a*b +*(a+1)*(b+1)+*(a+2)*(b+2)+DotProduct:result(a+3,b+3)=*a*b +*(a+1)*(b+1)+*(a+2)*(b+2)+*(a+3)*(b+3)+DotProduct:result(a+4,b+4)=*a*b +*(a+1)*(b+1)+*(a+2)*(b+2)+*(a+3)*(b+3)+*(a+4)*(b+4)值得一提的一个模板元编程陷阱长期以来,科学计算领域一直是Fortran的天下,采用运行期
19、C+实现的算法太慢而无法适应数值计算的要求,利用元编程、表达式模板以及更好的编译器、优化器,现代C+也可以很好地满足数值计算的要求。数值计算Todd Veldhuizen对C+和Fortran在科学计算领域的性能表现作了比较(参见注4)。实践证明,对于现代C+编程而言,元编程最大的用场本并不在于编译期数值计算,而是用于类型计算(type computation)(及相关领域)。通过类型参数、模板参数、typedef、枚举(或静态整型常量)以及内嵌类(模板)成员等,借助于灵活的类模板特化能力,模板元编程在类型计算方面可以释放出极大的能量。类型计算类型计算/仅声明struct Nil;/主模板te
20、mplate struct IsPointer enum Result=false;typedef Nil ValueType;/局部特化template struct IsPointer enum Result=true;typedef T ValueType;/示例int main()cout IsPointer:Result endl;cout IsPointer:Result endl;IsPointer:ValueType i=1;/IsPointer:ValueType j=1;/错误:使用未定义的类型Nil可 以 依 样 实 现 出 IsReference、IsClass、IsF
21、loat、IsMemberPointer等元函数,事实上,Boost Type Traits库提供了数十个类似的元函数以及像add_reference这样的低级类型操纵元函数,用于侦测、处理类型的基本属性。该库已经被纳入C+标准委员会技术报告(Technical Report),这预示着它将会进入C+0 x标准。前述的“循环开解”实际上就是一种代码生成机制,但模板元编程代码生成机制的作用并不局限于数值计算领域。代码生成struct A static void execute()cout A endl;struct B static void execute()cout B endl;int m
22、ain()IfThenElse:ResultType:execute();模板元程序充任“高级的预处理指令”等价template struct ScatterHierarchyTag;template class TList,template class Unit struct GenScatterHierarchy;/见下页前述的“循环开解”实际上就是一种代码生成机制,但模板元编程代码生成机制的作用并不局限于数值计算领域。代码生成此例摘自Loki&MCDstruct Nil;/仅声明struct Empty;/Typelisttemplate struct TypeList typedef
23、H Head;typedef T Tail;模板也是一种元数据代码生成/接上template class T1,class T2,template class Unitstruct GenScatterHierarchyTypeList,Unit :public GenScatterHierarchyScatterHierarchyTag,Unit ,public GenScatterHierarchy;template class T1,class T2,template class Unitstruct GenScatterHierarchyScatterHierarchyTag,Unit
24、 :public GenScatterHierarchy;template class AtomicType,template class Unitstruct GenScatterHierarchy:public Unit;template template class Unitstruct GenScatterHierarchy;处理Nil类型的情况什么都不做将一个非Typelist的原子类型传给Unit代码生成/自定义类型Teststruct Test enum Value=100;template struct Holder T Value;static void f()cout si
25、zeof(T)endl;/.;/定义一个包含有char、int、Test和float的Typelisttypedef TypeListchar,TypeListint,TypeListTest,TypeList CIRF;typedef GenScatterHierarchy SH;这个Holder模板决定了生成的各个类的能力代码生成int main()SH sh;cout (dynamic_castHolder&(sh).Value=a)endl;cout (dynamic_castHolder&(sh).Value=1)endl;cout (dynamic_castHolder&(sh).
26、Value=3.14f)endl;cout (dynamic_castHolder&(sh).Value.Value)endl;/cout (dynamic_castHolder&(sh).Value=3.14)endl;GenScatterHierarchy将给定的TypeList中的每一个类型施加于一个由用户提供的基本模板Holder上,从而产生一个类层次结构。换句话说,生成的各类的能力,取决于客户提供的Holder模板的能力。示例中生成了一个多重继承类层次结构,Holder,Holder,Holder,Holder之间没有任何关系,但它们都是SH的基类,即所产生的SH层次结构其实相当于:
27、class SH:public Holder,public Holder,public Holder,public Holder;代码生成利用元编程生成类层次结构最大的好处在于其灵活性:TypeList是可扩展的,其长度不但可以任意定制,而且对其更改后SH可以自适应地产生新的代码。Loki库中的Abstract Factory泛型模式即借助于这种机制实现在不损失类型安全性的前提下降低对类型的静态依赖性。进一步了解typelist和相关的代码生成技术,以及Abstract Factory泛型模式,参见注11和注15。可以利用元编程技术实现编译期断言和编译期约束。断言和契约断言用于指定程序中某些特
28、定点的条件应为“真”。如果该条件不为真,则断言失败,程序执行中断。大量使用断言可以在开发期捕捉许多错误。当然,若有可能,在编译期抓住错误更好。触发编译期断言的结果是导致程序无法通过编译。编译期断言的实现方式并非仅限于模板元编程一种。断言和契约/主模板templatestruct StaticAssert;/完全特化template struct StaticAssert;/辅助宏#define STATIC_ASSERT(exp)StaticAssert StaticAssertFailed;int main()STATIC_ASSERT(01);声明STATIC_ASSERT宏是为了方便使用
29、,否则用户如果写StaticAssert;在有些编译器(例如GCC和Borlad C+)中编译报错,在另外一些编译器(例如VC+和Digital Mars)中则可以编译通过。也就是说,单单“提及”一下StaticAssert是不保险的,要强迫它完全实例化才能保证触发断言,例如StaticAssert S;这看上去有些臆怪,因此我们把它封装到宏里:StaticAssert StaticAssertFailed;断言和契约命名为StaticAssertFailed是为了便于在生成的编译错误信息中看出触发了一个静态断言,例如在GCC中生成如下错误信息:sa.cpp:13:error:aggregat
30、e StaticAssert StaticAssertFailed has incomplete type and cannot be defined注意:表达式必须能够在编译期进行求值,无法对运行期表达式进行求值。这个错误信息还有改善的余地,你可以考察Boost和Loki中的静态断言库/组件,它们的实现更到位。一个新的关键字static_assert极有可能被加入C+0 x,其作用正如StaticAssert模板。断言和契约契约式设计(Design by Contract)要求为组件指定“契约”,这些契约会在程序运行过程中的某些特定点被强制执行。编译期契约也被称为约束(constraints
31、)。C+虽然不直接支持约束,但是我们可以手工实现这项技术。例如,结合运用上述StaticAssert和前面编写的IsPointer,可以实现一个约束:#define CONSTRAINT_MUST_BE_POINTER(T)STATIC_ASSERT(IsPointer:Result!=0)断言和契约在以下类模板中,通过将该约束放在析构函数中,通常可以保证模板参数T必须是一个指针类型:template class Testpublic:Test()/只要该类的实例被创建,约束就会发挥作用 CONSTRAINT_MUST_BE_POINTER(T);我们没有将它放置于构造函数中,因为构造函数可能
32、不止一个int main()Test r;/OK Test d;/违反约束,编译报错库C+模板的语法较复杂,一些惯用法应该采用库的方式提供。将这些复杂性封装于库中,并暴露给用户友好的接口,是每一个模板库开发者的责任,因为再复杂的库都应该是“面向用户”的。库中出现大量的繁杂的代码是可以接受的,因为这样的工作只需做一次,而所有库的用户均可从中受益。高质量的库可以为开发可移植、高性能的应用提供良好的基础,一个经过缜密设计和测试的库具有良好的复用性,可以屏蔽平台之间的差异,可以使程序员专注于自己的业务开发。知名的模板元编程库有Loki 注14、Boost(元编程库)注13、Blitz+注15以及MTL
33、 注16等。库Blitz+核心库的开发者是模板元编程技术的创始人Todd Veldhuizen,可以说是最早利用模板将运行期计算转移至编译期的库,主要提供了对向量、矩阵等进行处理的线性代数计算。长期以来,科学计算一直是Fortran77/90的地盘,而Blitz+利用元编程(以及表达式模板等)技术可以获得和Fortran77/90媲美的效率(参见注4)。Loki 将模板元编程在类型计算方面的威力应用于设计模式领域,利用元编程(以及其他一些重要的设计技术)实现了一些常见的设计模式之泛型版本。库Boost 元编程库目前主要包含MPL、Type Traits和Static Assert等库。Stat
34、ic Assert和Type Traits用作MPL的基础。MPL是一个通用的模板元编程框架,它仿照STL提供了编译期算法、序列、迭代器等元编程组件。它为普通程序员进行元编程提供了高级抽象,使得元编程变得容易、高效、富有乐趣。Boost Type Traits库包含一系列traits类,用于萃取C+类型特征。另外还包含了一些转换traits(例如移除一个类型的const修饰符等)。Boost Static Assert库用于编译期断言,如果评估的表达式编译时计算结果为true,则代码可以通过编译,否则编译报错。DSEL对于领域特定的嵌入式语言(domain-specific embedded
35、language,DSEL)的设计者而言,模板元编程技术是一件利器。这里的DSEL就是库,例如一些图形或矩阵计算库都可被认为是一种小型语言:其接口定义语法,其实现定义语义。它们均提供了领域特定的符号、构造和抽象。利用模板元编程技术构建的DSEL,高效且语法接近于从头构建的语言。由于这种DSEL采用纯粹的C+编写,与使用独立的DSL相比,无需再使用专门的编译器、编辑器等工具,从而可以消除跨语言边界所需付出的代价。如欲进一步了解采用C+模板元编程实现DSEL,参见注9。DSELint a510;int i;for_each(a,a+5,for_loop(var(i)=0,var(i)10,+var
36、(i),_1var(i)+=1);std:for_each(v.begin(),v.end(),(switch_statement(_1,case_statement(std:cout constant(zero),case_statement(std:cout constant(one),default_statement(cout constant(other:)_1),cout constant(n);摘自Boost Lambda库的几个例子:DSELfor_each(a.begin(),a.end(),try_catch(bind(foo,_1),/foo may throw catc
37、h_exception (cout constant(Caught foo_exception:)foo was called with argument=_1 ),catch_exception (cout constant(Caught std:exception:)bind(&std:exception:what,_e),throw_exception(bind(constructor(),_1),catch_all(cout constant(Unknown),rethrow();结语模板元程序与常规代码结合使用时,此时源代码包含两种程序:常规C+运行期程序和编译期运行的模板元程序。当
38、被编译器解释时,模板元程序可以生成高效的代码,从而可以大幅提高最终应用程序的运行效率。通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。模板元编程也有一些局限性,使用模板元编程时(尤其使用模板元编程进行数值计算时)存在一些注意事项调试困难:元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。代码的可读性较差。结语结果程序性能未必一定最优化:“循环开解”技术要有选择地使用,具体获得的效果必须进行评测。倘若代码展开导致程序尺寸过大,可能会降低cac
39、he的命中率,未必会带来性能上的提高。编译器局限性:模板的实例化通常要占用不少编译器资源,大量的递归实例化会迅速拖慢编译器甚至耗尽可用资源。David Abrahams在C+Template Metaprogramming一书中介绍的“递归开解(Recursion Unrolling)”技术,可以“突破”编译器对模板实例化层数的限制。Boost MPL具有内建的“递归开解“功能。编译时间延长:元程序被编译器解释执行的,C+编译器并不是一个好的解释器。可移植性较差:对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。资源以下是一些C+模板元编程资源,它们或者为本幻灯片的制作提供了参考素材
40、,或者提供了延伸知识。2 Todd Veldhuizen,Expression Templates,http:/osl.iu.edu/tveldhui/papers/Expression-Templates/exprtmpl.html1 Todd Veldhuizen,Template Metaprograms,http:/osl.iu.edu/tveldhui/papers/Template-Metaprograms/meta-art.html文章资源3 Todd Veldhuizen,C+templates as partial evaluation,http:/osl.iu.edu/tv
41、eldhui/papers/pepm99/.5 Erwin Unruh,Prime numbers(Primzahlen-Original),http:/www.erwin-unruh.de/primorig.html.6 Erwin Unruh,Prime numbers(Primzahlen),http:/www.erwin-unruh.de/Prim.html.4 Todd Veldhuizen,Scientific Computing:C+versus Fortran,http:/osl.iu.edu/tveldhui/papers/DrDobbs2/drdobbs2.html7 Ed
42、ward Rosten,Floating point arithmetic in C+templates http:/mi.eng.cam.ac.uk/er258/code/fp_template.html.资源8 David Vandervoode and Nicolai M.Josuttis.C+Templates:The Complete Guide.Addison-Wesley11 Andrei Alexandrescu.Modern C+Design:Generic Programming and Design Patterns Applied.Addison-Wesley9 Dav
43、id Abrahams,Aleksey Gurtovoy,C+Template Metaprogramming:Concepts,Tools,and Techniques from Boost and Beyond,Addison-Wesley Professional.12 Krysztof Czarnecki,Ulrich Eisenecker,Generative Programming:Methods,Tools,and Applications,Addison-Wesley Professional书籍10 Matthew Wilson,Imperfect C+:Practical Solutions for Real-Life Programming,Addison-Wesley Professional.资源13 Boost http:/www.boost.org/库14 Blitz+http:/www.oonumerics.org/blitz/15 Loki http:/ MTL http:/www.osl.iu.edu/research/mtl/17 FC+http:/www.cc.gatech.edu/yannis/fc+/The End
限制150内