转自 http://blog.csdn.net/zhoudaxia/article/details/4456571
(1)基本的Stack:以std::deque为内部容器。方法有入栈、出栈、返回栈顶元、判断栈空。
- //stack1.h:栈的基本实现
- #ifndef __STACK1_H__
- #define __STACK1_H__
- #include <deque>
- #include <stdexcept>
- template<typename T>
- class Stack{
- private:
- std::deque<T> elems; //包含元素的容器
- public:
- void push(T const&); //压入元素
- void pop(); //弹出元素
- T top() const; //返回栈顶元素
- bool empty() const{ //判断栈是否为空
- return elems.empty();
- }
- };
- template<typename T>
- void Stack<T>::push(T const& elem){
- elems.push_back(elem); //把元素的拷贝添加到末端
- }
- template<typename T>
- void Stack<T>::pop(){
- if(elems.empty()){
- throw std::out_of_range("Stack<T>::pop(): empty stack");
- }
- elems.pop_back(); //删除末端元素
- }
- template<typename T>
- T Stack<T>::top() const{
- if(elems.empty()){
- throw std::out_of_range("Stack<T>::top(): empty stakc");
- }
- return elems.back(); //返回末端元素的拷贝
- }
- #endif
测试程序:
- //stack1test.cpp:测试Stack
- #include <iostream>
- #include <string>
- #include <cstdlib>
- #include "stack1.h"
- int main(){
- try{
- Stack<int> intStack; //元素类型为int的栈
- Stack<std::string> stringStack; //元素类型为string的栈
- intStack.push(7);
- std::cout<<intStack.top()<<std::endl;
- stringStack.push("hello");
- std::cout<<stringStack.top()<<std::endl;
- stringStack.pop();
- stringStack.pop(); //会抛出std::out_of_range异常
- }catch(std::exception const& ex){ //捕捉到这个异常并打印出来
- std::cerr<<"Exception: "<<ex.what()<<std::endl;
- return EXIT_FAILURE;
- }
- }
(2)改进1:指定栈的容量,要用到非类型模板参数。由于栈的容量固定,可以用数组来存放元素。
- //stack2.h:容量受限的栈
- #ifndef __STACK2_H__
- #define __STACK2_H__
- #include <stdexcept>
- template<typename T,int size>
- class Stack{
- T elems[size]; //由于容量受限,可用换成用数组来管理元素
- int numElems; //元素的当前总个数
- public:
- Stack(); //构造函数
- void push(T const&); //压入元素
- void pop(); //弹出元素
- T top() const; //返回栈顶元素
- bool empty() const{ //判断栈是否为空
- return numElems==0;
- }
- bool full() const{ //判断栈是否已满
- return numElems==size;
- }
- };
- template<typename T,int size>
- Stack<T,size>::Stack():numElems(0){ //初始时不含元素
- //不做任何事
- }
- template<typename T,int size>
- void Stack<T,size>::push(T const& elem){
- if(numElems==size)
- throw std::out_of_range("Stack<T,size>::push(): stack is full");
- elems[numElems]=elem; //附加元素
- ++numElems; //增加元素个数
- }
- template<typename T,int size>
- void Stack<T,size>::pop(){
- if(numElems<=0)
- throw std::out_of_range("Stack<T,size>::pop(): empty stack");
- --numElems; //减少元素个数
- }
- template<typename T,int size>
- T Stack<T,size>::top() const{
- if(numElems<=0)
- throw std::out_of_range("Stack<>::top(): empty stack");
- return elems[numElems-1]; //返回末端元素
- }
- #endif
测试程序:
- //stack2test.cpp:测试容量受限的Stack
- #include <iostream>
- #include <string>
- #include <cstdlib>
- #include "stack2.h"
- int main(){
- try{
- Stack<int,20> int20Stack; //可存储20个int元素的栈
- Stack<int,40> int40Stack;
- Stack<std::string,40> stringStack;
- int20Stack.push(7);
- std::cout<<int20Stack.top()<<std::endl;
- int20Stack.pop();
- stringStack.push("hello");
- std::cout<<stringStack.top()<<std::endl;
- stringStack.pop();
- stringStack.pop();
- }catch(std::exception const& ex){
- std::cerr<<"Exception: "<<ex.what()<<std::endl;
- return EXIT_FAILURE;
- }
- return 0;
- }
(3)改进2:可以指定栈内部使用的容器,还可以使 不同类型的栈之间能赋值。要用到模板模板参数。要定义一个赋值运算符的成员模板,它并不会覆盖缺省赋值运行符,对于相同类型栈之间的赋值,仍然会调用缺省的赋值运行符。由于容器有两个模板参数,一个是元素类型,一个是分配器类型,因此定义Stack的内部容器这个模板模板参数时,必须两个模板参数都要有。若只定义一个元素类型的参数,你传入容器(如vector,deque)时,并不能匹配。总之我们定义的模板模板参数必须与我们传入的模板精确匹配。
- //stack3.hpp:可以指定内部容器,并且不同类型的栈之间可以赋值的栈
- #ifndef STACK_HPP
- #define STACk_HPP
- #include <deque>
- #include <stdexcept>
- #include <memory>
- template<typename T,template<typename ELEM,typename ALLOC=std::allocator<ELEM> >
- class CONT=std::deque>
- class Stack{
- CONT<T> elems; //存放元素的窗口
- public:
- void push(T const&); //压入元素
- void pop(); //弹出元素
- T top() const; //返回栈顶元素
- bool empty() const{ //判断栈是否为空
- return elems.empty();
- }
- /* 这个赋值运算符是一个独立的成员模板,它可以把Stack<T2,CONT2<ELEM2> >类型的栈赋值Stack<T,CONT<ELEM> >型的栈,
- 只要ELEM2型可以隐式转换成ELEM型。它并没有覆盖缺省赋值运行符,对于相同类型栈之间的赋值,仍然会调用缺省的赋值运行符
- */
- template<typename T2,template<typename ELEM2,typename ALLOC=std::allocator<ELEM2> >
- class CONT2>
- Stack<T,CONT>& operator=(Stack<T2,CONT2> const&);
- };
- /* 由于并不需要用到参数ELEM和ALLOC,故可以省略 */
- template<typename T,template<typename,typename> class CONT>
- void Stack<T,CONT>::push(T const& elem){
- elems.push_back(elem); //把元素的拷贝添加到末端
- }
- template<typename T,template<typename,typename> class CONT>
- void Stack<T,CONT>::pop(){
- if(elems.empty())
- throw std::out_of_range("Stack<>::pop(): empty stack");
- elems.pop_back(); //删除末端元素
- }
- template<typename T,template<typename,typename> class CONT>
- T Stack<T,CONT>::top() const{
- if(elems.empty())
- throw std::out_of_range("Stack<>::top(): empty stack");
- return elems.back(); //返回末端元素的拷贝
- }
- template<typename T,template<typename,typename> class CONT>
- template<typename T2,template<typename,typename> class CONT2>
- Stack<T,CONT>& Stack<T,CONT>::operator=(Stack<T2,CONT2> const& rhs){
- if((void*)this==(void*)&rhs) //如果是自我赋值
- return *this;
- Stack<T2,CONT2> tmp(rhs);
- elems.clear(); //删除所有现存的元素
- while(!tmp.empty()){
- //把rhs栈中元素拷贝到本栈中,并且元素的顺序不变
- elems.push_front(tmp.top());
- tmp.pop();
- }
- return *this;
- }
- #endif
测试程序:
- //stack3test.cpp:测试改进型栈的功能
- #include <iostream>
- #include <string>
- #include <cstdlib>
- #include <vector>
- #include "stack3.hpp"
- int main(){
- try{
- Stack<int> intStack; //int栈
- Stack<float> floatStack; //float栈
- intStack.push(42);
- intStack.push(7);
- floatStack.push(7.7);
- floatStack=intStack; //不同类型的栈之间的赋值
- std::cout<<floatStack.top()<<std::endl;
- floatStack.pop();
- std::cout<<floatStack.top()<<std::endl;
- floatStack.pop();
- std::cout<<floatStack.top()<<std::endl;
- floatStack.pop();
- }catch(std::exception const& ex){
- std::cerr<<"Exception: "<<ex.what()<<std::endl;
- }
- Stack<int,std::vector> vStack; //使用vector作为内部容器的int栈
- vStack.push(42);
- vStack.push(7);
- std::cout<<vStack.top()<<std::endl;
- vStack.pop();
- return 0;
- }
=========================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4469954
C++中的模板产生类代码的过程非常复杂,而模板所表示的泛型代码还要依赖于使用模板的客户端。比如模板本身的位置、使用模板的位置、定义模板实参的位置等都会对模板的实例化产生影响。因此,模板代码的测试和调试都比较难。跟踪程序(tracer)就是一种可以跟踪模板实际调用步骤的程序,它通常是一个用户自定义的类,只定义了满足模板测试的一些功能。类的每个操作中都有一个针对该操作的跟踪(比如递增一个计数变量,以记录该操作的调用次数)。用这个类作为模板的实参来使用模板,就可以测试模板的功能。
跟踪程序是一种比较简单和行之有效的技术,它虽不能测试出模板代码中的所有错误,但可以在开发周期的早期检测出模板定义中的很多问题,这样就可以减轻后期调试的负担。当我们用C++开发模板代码时,使用跟踪程序是一种比较好的测试技巧。
下面开发一个跟踪程序Tracer,并用它测试排序算法std::sort(这是一个函数模板),这可以确认算法的效率和操作的实际调用步骤。类Tracer是跟踪程序,它可以跟踪算法运行时产生对象拷贝的份数、调用构造函数的次数、调用析构函数的次数、对象赋值的次数、对象比较的次数、现存对象的最大个数等。
- //tracer.hpp:跟踪程序,一个我们定义的类
- #include <iostream>
- class Tracer{
- private:
- int value; //要被排序的整数值
- int generation; //一个对象拷贝的份数
- static long n_created; //调用构造函数的次数
- static long n_destroyed; //调用析构函数的次数
- static long n_assigned; //赋值的次数
- static long n_compared; //比较的次数
- static long n_max_live; //现存对象的最大个数
- //重新计算现存对象的最大个数
- static void update_max_live(){
- if(n_created-n_destroyed>n_max_live){
- n_max_live=n_created-n_destroyed;
- }
- }
- public:
- static long creations(){
- return n_created;
- }
- static long destructions(){
- return n_destroyed;
- }
- static long assignments(){
- return n_assigned;
- }
- static long comparisons(){
- return n_compared;
- }
- static long max_live(){
- return n_max_live;
- }
- public:
- //构造函数
- Tracer(int v=0):value(v),generation(1){ //首次创建:产生对象的第一份拷贝
- ++n_created;
- update_max_live();
- //表示第n_created次创建对象(直接创建),被创建为第generation份拷贝
- //(首次创建时表示第1份拷贝),total表示现存对象的总个数
- std::cerr<<"Tracer creation #"<<n_created
- <<", created as generation "<<generation
- <<" (total: "<<n_created-n_destroyed<<")/n";
- }
- //拷贝构造函数
- Tracer(Tracer const& b):value(b.value),generation(b.generation+1){ //给对象b增加一份拷贝
- ++n_created;
- update_max_live();
- //表示第n_created次创建对象(复制创建),被复制为第generation份拷贝
- std::cerr<<"Tracer creation #"<<n_created
- <<", copied as generation "<<generation
- <<" (total: "<<n_created-n_destroyed<<")/n";
- }
- //析构函数
- ~Tracer(){
- ++n_destroyed;
- update_max_live();
- //表示第n_destroyed次销毁对象,对象当前的第generation份拷贝被销毁
- std::cerr<<"Tracer destruction #"<<n_destroyed<<", generation "<<generation
- <<" destroyed (total: "<<n_created-n_destroyed<<")/n";
- }
- //赋值运算符
- Tracer& operator=(Tracer const& b){
- if(this==&b) return *this;
- ++n_assigned;
- //表示第n_assigned次赋值:把右边对象的第b.generation份拷贝赋给左边对象的第generation份拷贝
- std::cerr<<"Tracer assignment #"<<n_assigned
- <<" (generation "<<generation<<" = "<<b.generation<<")/n";
- value=b.value;
- return *this;
- }
- //比较运算符
- friend bool operator<(Tracer const& a,Tracer const& b){
- ++n_compared;
- //表示第n_compared次比较:把左边对象的第a.generation份拷贝与右边对象
- //的第b.generation份拷贝作<比较
- std::cerr<<"Tracer comparison #"<<n_compared
- <<" (generation "<<a.generation<<" < "<<b.generation<<")/n";
- return a.value<b.value;
- }
- int val() const {
- return value;
- }
- };
- long Tracer::n_created=0;
- long Tracer::n_destroyed=0;
- long Tracer::n_max_live=0;
- long Tracer::n_assigned=0;
- long Tracer::n_compared=0;
用这个跟踪程序来测试排序算法。如下:
- //tracertest.cpp:用跟踪程序来测试std::sort这个函数模板的功能
- #include <iostream>
- #include <algorithm>
- #include "tracer.hpp"
- int main(){
- //准备输入的例子:构造函数会对给定的整数值执行隐式转换
- Tracer input[]={7,3,5,6,4,2,0,1,9,8};
- //输出初始值
- for(int i=0;i<10;++i)
- std::cerr<<input[i].val()<<' ';
- std::cerr<<std::endl;
- //存取初始状态
- long created_at_start=Tracer::creations();
- long max_live_at_start=Tracer::max_live();
- long assigned_at_start=Tracer::assignments();
- long compared_at_start=Tracer::comparisons();
- //执行算法
- std::cerr<<"/n/n---[ Start std::sort() ]--------------------------------------------------/n/n";
- std::sort<>(&input[0],&input[9]+1);
- std::cerr<<"/n/n---[ End std::sort() ]----------------------------------------------------/n/n";
- //确认结果
- for(int i=0;i<10;++i)
- std::cerr<<input[i].val()<<' ';
- std::cerr<<"/n/n";
- //最后的输出报告
- std::cerr<<"Statistics: "<<std::endl;
- std::cerr<<"std::sort() of 10 Tracer's was performed by:/n"
- <<Tracer::creations()-created_at_start<<" temporary tracers/n"<<"up to "
- <<Tracer::max_live()<<" tracers at the same time ("
- <<max_live_at_start<<" before)/n"
- <<Tracer::assignments()-assigned_at_start<<" assignments/n"
- <<Tracer::comparisons()-compared_at_start<<" comparsions/n/n";
- }
运行结果如下:
- zhouhuansheng@laptop-zhou:~/zhou/code/cpp_templates/basics$ g++ -Wall tracertest.cpp -o tracertest
- zhouhuansheng@laptop-zhou:~/zhou/code/cpp_templates/basics$ ./tracertest
- Tracer creation #1, created as generation 1 (total: 1)
- Tracer creation #2, created as generation 1 (total: 2)
- Tracer creation #3, created as generation 1 (total: 3)
- Tracer creation #4, created as generation 1 (total: 4)
- Tracer creation #5, created as generation 1 (total: 5)
- Tracer creation #6, created as generation 1 (total: 6)
- Tracer creation #7, created as generation 1 (total: 7)
- Tracer creation #8, created as generation 1 (total: 8)
- Tracer creation #9, created as generation 1 (total: 9)
- Tracer creation #10, created as generation 1 (total: 10)
- 7 3 5 6 4 2 0 1 9 8
- ---[ Start std::sort() ]--------------------------------------------------
- Tracer creation #11, copied as generation 2 (total: 11)
- Tracer comparison #1 (generation 2 < 1)
- Tracer assignment #1 (generation 1 = 1)
- Tracer assignment #2 (generation 1 = 2)
- Tracer destruction #1, generation 2 destroyed (total: 10)
- Tracer creation #12, copied as generation 2 (total: 11)
- Tracer comparison #2 (generation 2 < 1)
- Tracer creation #13, copied as generation 3 (total: 12)
- Tracer comparison #3 (generation 3 < 1)
- Tracer assignment #3 (generation 1 = 1)
- Tracer comparison #4 (generation 3 < 1)
- Tracer assignment #4 (generation 1 = 3)
- Tracer destruction #2, generation 3 destroyed (total: 11)
- Tracer destruction #3, generation 2 destroyed (total: 10)
- Tracer creation #14, copied as generation 2 (total: 11)
- Tracer comparison #5 (generation 2 < 1)
- Tracer creation #15, copied as generation 3 (total: 12)
- Tracer comparison #6 (generation 3 < 1)
- Tracer assignment #5 (generation 1 = 1)
- Tracer comparison #7 (generation 3 < 1)
- Tracer assignment #6 (generation 1 = 3)
- Tracer destruction #4, generation 3 destroyed (total: 11)
- Tracer destruction #5, generation 2 destroyed (total: 10)
- Tracer creation #16, copied as generation 2 (total: 11)
- Tracer comparison #8 (generation 2 < 1)
- Tracer creation #17, copied as generation 3 (total: 12)
- Tracer comparison #9 (generation 3 < 1)
- Tracer assignment #7 (generation 1 = 1)
- Tracer comparison #10 (generation 3 < 1)
- Tracer assignment #8 (generation 1 = 1)
- Tracer comparison #11 (generation 3 < 1)
- Tracer assignment #9 (generation 1 = 1)
- Tracer comparison #12 (generation 3 < 1)
- Tracer assignment #10 (generation 1 = 3)
- Tracer destruction #6, generation 3 destroyed (total: 11)
- Tracer destruction #7, generation 2 destroyed (total: 10)
- Tracer creation #18, copied as generation 2 (total: 11)
- Tracer comparison #13 (generation 2 < 1)
- Tracer assignment #11 (generation 1 = 1)
- Tracer assignment #12 (generation 1 = 1)
- Tracer assignment #13 (generation 1 = 1)
- Tracer assignment #14 (generation 1 = 1)
- Tracer assignment #15 (generation 1 = 1)
- Tracer assignment #16 (generation 1 = 2)
- Tracer destruction #8, generation 2 destroyed (total: 10)
- Tracer creation #19, copied as generation 2 (total: 11)
- Tracer comparison #14 (generation 2 < 1)
- Tracer assignment #17 (generation 1 = 1)
- Tracer assignment #18 (generation 1 = 1)
- Tracer assignment #19 (generation 1 = 1)
- Tracer assignment #20 (generation 1 = 1)
- Tracer assignment #21 (generation 1 = 1)
- Tracer assignment #22 (generation 1 = 1)
- Tracer assignment #23 (generation 1 = 2)
- Tracer destruction #9, generation 2 destroyed (total: 10)
- Tracer creation #20, copied as generation 2 (total: 11)
- Tracer comparison #15 (generation 2 < 1)
- Tracer creation #21, copied as generation 3 (total: 12)
- Tracer comparison #16 (generation 3 < 1)
- Tracer assignment #24 (generation 1 = 1)
- Tracer comparison #17 (generation 3 < 1)
- Tracer assignment #25 (generation 1 = 1)
- Tracer comparison #18 (generation 3 < 1)
- Tracer assignment #26 (generation 1 = 1)
- Tracer comparison #19 (generation 3 < 1)
- Tracer assignment #27 (generation 1 = 1)
- Tracer comparison #20 (generation 3 < 1)
- Tracer assignment #28 (generation 1 = 1)
- Tracer comparison #21 (generation 3 < 1)
- Tracer assignment #29 (generation 1 = 1)
- Tracer comparison #22 (generation 3 < 1)
- Tracer assignment #30 (generation 1 = 3)
- Tracer destruction #10, generation 3 destroyed (total: 11)
- Tracer destruction #11, generation 2 destroyed (total: 10)
- Tracer creation #22, copied as generation 2 (total: 11)
- Tracer comparison #23 (generation 2 < 1)
- Tracer creation #23, copied as generation 3 (total: 12)
- Tracer comparison #24 (generation 3 < 1)
- Tracer assignment #31 (generation 1 = 3)
- Tracer destruction #12, generation 3 destroyed (total: 11)
- Tracer destruction #13, generation 2 destroyed (total: 10)
- Tracer creation #24, copied as generation 2 (total: 11)
- Tracer comparison #25 (generation 2 < 1)
- Tracer creation #25, copied as generation 3 (total: 12)
- Tracer comparison #26 (generation 3 < 1)
- Tracer assignment #32 (generation 1 = 1)
- Tracer comparison #27 (generation 3 < 1)
- Tracer assignment #33 (generation 1 = 3)
- Tracer destruction #14, generation 3 destroyed (total: 11)
- Tracer destruction #15, generation 2 destroyed (total: 10)
- ---[ End std::sort() ]----------------------------------------------------
- 0 1 2 3 4 5 6 7 8 9
- Statistics:
- std::sort() of 10 Tracer's was performed by:
- 15 temporary tracers
- up to 12 tracers at the same time (10 before)
- 33 assignments
- 27 comparsions
- Tracer destruction #16, generation 1 destroyed (total: 9)
- Tracer destruction #17, generation 1 destroyed (total: 8)
- Tracer destruction #18, generation 1 destroyed (total: 7)
- Tracer destruction #19, generation 1 destroyed (total: 6)
- Tracer destruction #20, generation 1 destroyed (total: 5)
- Tracer destruction #21, generation 1 destroyed (total: 4)
- Tracer destruction #22, generation 1 destroyed (total: 3)
- Tracer destruction #23, generation 1 destroyed (total: 2)
- Tracer destruction #24, generation 1 destroyed (total: 1)
这里“Tracer creation #1, created as generation 1 (total: 1)“表示第1次创建对象(直接创建),被创建为第generation份拷贝(首次创建时表示第1份拷贝,以后调用复制构造函数时,拷贝份数会增加),total表示现存对象的总个数。
“Tracer creation #11, copied as generation 2 (total: 11)“表示第11次创建对象(通过复制构造函数创建),被复制为第generation份拷贝。
“Tracer comparison #1 (generation 2 < 1)“表示第1次比较,比较左边对象的第2份拷贝是否小于右边对象的第1份拷贝。
“Tracer assignment #1 (generation 1 = 1)“表示第1次赋值:把右边对象的第1份拷贝赋给左边对象的第1份拷贝。
“Tracer destruction #1, generation 2 destroyed (total: 10)“表示第1次销毁对象,当前对象的第2份拷贝被销毁。
从最后的统计报告中可以看出,对10个对象运行std::sort算法进行排序,总共创建了15个临时对象,但在同一时刻最多只存在2个多余的对象(最多有12个,减去要排序的10),这让我们对算法的开销有了基本的把握。运行也说明了我们的tracer完全满足标准sort()算法的要求,例如不需要运算符==和运算符>。从输出的排序结果可以看出基本上没有问题。特别要注意这只是一个大概的判断,它并不能断定算法的内部代码实现的完全正确性。
对tracer增加断言和推理,或者连接到推理引擎(是一个程序,可以记住用来推导出结论的断言和推理)等,就可以扩展成所谓的oracles(测试谕示)。oracles可以动态地验证算法,它不需要完全指定模板实参(oracles本身就是实参),也不需要在程序中指定输入数据(推理引擎会请求用户来输入)。当前使用oracles对算法复杂度的分析也是非常有限的。
tracer是跟踪模板所需要的最小接口。当tracer不产生运行期输出时,我们有时也称之为archetype(原型)。利用archetype我们可以验证模板是否会请求不符合期望的语法约束。对于一个模板的实现,我们一般要为模板库中标记的每个concept都开发一个archetype。
===============================================================================
多态polymorphism是指具有多种形态的情况,它能根据单一的标记关联不同的行为。多态是面向对象程序设计的基础。在面向对象程序设计中的多态是一种运行时的多态。C++中有两种多态,称为动多态(运行时多态)和静多态(编译时多态),而静多态主要通过模板来实现,宏也是实现静多态的一种途径。其实在做软件设计时静多态的威力也是非常强大的,只不过我们经常对它疏忽了而已。
动多态的设计思想:对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到你实际提供的子类对象上去。
下面以几何对象的设计为例。对各种几何对象如圆、矩形、直线等,都有一些共同的操作,比如画出几何对象,有重心等,我们把这些接口抽象成虚函数放在所谓的抽象基类GeoObj中,具体的几何对象类则继承这个抽象基类。如下:
- //dynahier.hpp:几何类的定义
- #ifndef __GEOOBJ_H__
- #define __GEOOBJ_H__
- #include "coord.hpp"
- class GeoObj{ //几何对象的公共抽象基类
- public:
- virtual void draw() const=0; //画出几何对象
- virtual Coord center_of_gravity() const=0; //返回几何对象的重心
- //...
- };
- class Circle : public GeoObj{ //具体的几何对象类:圆
- public:
- virtual void draw() const;
- virtual Coord center_of_gravity() const;
- //...
- };
- class Line : public GeoObj{ //直线类
- public:
- virtual void draw() const;
- virtual Coord center_of_gravity() const;
- //...
- };
- //...
- #endif
客户端的使用如下:
- //dynapoly.cpp:客户端代码
- #include "dynahier.hpp"
- #include <vector>
- void myDraw(GeoObj const& obj){ //画任意一个GeoObj对象
- obj.draw(); //根据对象类型来调用对应的draw()
- }
- Coord distance(GeoObj const& x1,GeoObj const& x2){ //计算两个GeoObj对象的重心之间的距离
- Coord c=x1.center_of_gravity()-x2.center_of_gravity();
- return c.abs(); //返回坐标的绝对值
- }
- void drawElems(std::vector<GeoObj*> const& elems){ //画出属于异类集合的GeoObj对象
- for(std::size_t i=0;i<elems.size();++i)
- elems[i]->draw(); //根据元素类型来调用相应的draw()
- }
- int main(){
- Line l;
- Circle c,c1,c2;
- myDraw(l); //myDraw(GeoObj&) => Line::draw()
- myDraw(c); //myDraw(GeoObj&) => Circle::draw()
- distance(c1,c2); //distance(GeoObj&,GeoObj&)
- distance(l,c); //distance(GeoObj&,GeoObj&)
- std::vector<GeoObj*> coll; //元素类型互异的集合
- coll.push_back(&l); //插入一条直线
- coll.push_back(&c); //插入一个圆
- drawElems(coll); //画不同种类的GeoObj对象
- return 0;
- }
静多态的设计思想: 对于相关的对象类型,直接实现它们各自的定义,不需要基类。只是隐式地规定各个具体类的实现中相同功能的接口名要相同。客户端把操作这些对象的函数定义为模板,你需要操作什么类型的对象,直接对模板指定该类型实参即可(或通过实参演绎获得)。
改写上面的设计,如下:
- //statichier.hpp:几何类的定义
- #ifndef __GEOOBJ_H__
- #define __GEOOBJ_H__
- #include "coord.hpp"
- class Circle{ //具体的几何对象类Circle,并没有派生自其他类
- public:
- void draw() const; //非虚函数
- Coord center_of_gravity() const;
- //...
- };
- class Line{ //直线类Line
- public:
- void draw() const;
- Coord center_of_gravity() const;
- //...
- };
- //...
- #endif
- //staticpoly.cpp:客户端代码
- #include "statichier.hpp"
- #include <vector>
- template<typename GeoObj>
- void myDraw(GeoObj const& obj){ //画任意一个GeoObj对象
- obj.draw(); //根据对象类型来调用对应的draw()
- }
- template<typename GeoObj1,typename GeoObj2>
- Coord distance(GeoObj1 const& x1,GeoObj2 const& x2){ //计算两个GeoObj对象的重心之间的距离
- Coord c=x1.center_of_gravity()-x2.center_of_gravity();
- return c.abs(); //返回坐标的绝对值
- }
- template<typename GeoObj>
- void drawElems(std::vector<GeoObj> const& elems){ //画出属于异类集合的GeoObj对象
- for(std::size_t i=0;i<elems.size();++i)
- elems[i].draw(); //根据元素类型来调用相应的draw()
- }
- int main(){
- Line l;
- Circle c,c1,c2;
- myDraw(l); //myDraw<Line>(Line&) => Line::draw()
- myDraw(c); //myDraw<Circle>(Circle&) => Circle::draw()
- distance(c1,c2); //distance<Circle,Circle>(Circle&,Circle&)
- distance(l,c); //distance<Line,Circle>(Line&,Circle&)
- //std::vector<GeoObj*> coll; //错误:异类集合在这里不允许
- std::vector<Line> coll; //正确:同类集合在这里是允许的
- coll.push_back(l); //插入一条直线
- drawElems(coll); //画出所有直线
- return 0;
- }
两种多态设计范式的比较:
(1)动多态的特点:通过继承实现、接口预先在基类中确定、是动态的(运行期绑定接口)。
优点:能处理异类集合(容器中存储基类指针即可)、可执行代码比较小(只需一个多态函数)、可以完全编译而不需要发布源码。
缺点:不能提前检查类型的安全性(如向容器中插入错误类型的对象)、性能低(有层层继承)、耦合性高(继承的耦合性高于组合)。
(2)静多态的特点:通过模板实现(宏也是实现静多态的一种途径)、接口没有预先确定而只是隐式地规定、是静态的(编译期绑定接口)。
优点:具体类可以只实现需要的接口、生成代码性能高(无需通过指针的间接调用,非虚函数具有更多的内联机会)、有更好的类型安全性(类型在编译期就进行检查)、耦合性低(各个类相互独立)、集合的元素类型不再局限于指针。
缺点:不能处理异类集合、可执行代码比较大(代码膨胀)、模板库源码需要发布、对模板实参类型有约束(比如需要该类型实现了operator<)。
现实中我们可以组合两种多态来做设计:从公共基类派生不同的子类,从而能够处理属于异类集合的不同对象。而需要操作具体某种类型的对象时,使用模板来实现。
实际上,泛型程序设计依赖的就是静多态。C++中的一个泛型程序设计杰作就是STL。STL库中的设计思想是把对象的集合抽象为容器,把对对象的操作抽象为算法。算法和容器都是模板(即静多态方案),这样算法可以独立出来,而不是容器的成员函数。一个算法可以被多种容器使用。为了让容器能够使用算法,从容器中抽象出迭代器的概念,通过把容器的迭代器传给算法,就可以在容器上执行算法的行为。
迭代器是泛型程序设计的粘合剂,它由容器提供并能被算法所使用。迭代器之所以能作粘合剂,是由于容器为迭代器提供了一些特定的接口,而算法所使用的正是这些接口。一般把这样的接口称为concept(即约束)。
从原则上讲,我们也可以用动多态来实现STL,然而这肯定会带来很多的麻烦。与迭代器的概念相比,动多态的虚函数调用机制将会是一种重量级的实现机制,比如增加一层基于虚函数的接口层,通常这会对性能和效率产生很大的影响,有可能是几个数量级。可见,静多态也是一种威力非常强大的工具,只要应用得好,通过C++的模板,我们可以设计出抽象程度高、性能很好的软件组件。
==================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4486487
我们知道,类有属性(即数据)和操作两个方面。同样模板也有自己的属性(特别是模板参数类型的一些具体特征,即trait)和算法策略(policy,即模板内部的操作逻辑)。模板是对有共性的各种类型进行参数化后的一种通用代码,但不同的具体类型又可能会有一些差异,比如不同的类型可能会有自己的不同特征和算法实现策略。trait模板和policy模板技术就是把模板的trait和policy这两个针对不同具体类型有变化的方面抽离出来形成两个独立的模板。由于trait和policy本身是模板,它的行为是可配置的(根据原来模板的具体实参类型),因此在模板中通过组合或者以模板实参传进来的方式使用trait和policy,就可以配置出不同的具体实现。
1、trait模板技术: 当在模板代码中需要知道类型参数T的某些特征(比如需要知道T是哪个具体类型,是否有默认构造函数,希望该类型有合理的缺省值,如int型缺省值为0),我们可以声明一个描述T的特征的trait<T>模板,然后对每种具体类型(如int,char,用户定义的类)特化trait<T>,在各特化版本中用typedef为该具体类型(或者想映射成的其他类型)定义统一的别名(比如AliT),根据需要还可指定合理的缺省值等。这样在原来模板文件中#include这个trait<T>模板的文件,就可以在模板代码中使用trait<T>::AliT来获得T的具体特征。
比如我们要计算数组各个元素的累加和,由于数组元素可以是各种类型,我们使用模板来实现它,这时有一个类型参数T。但在算法代码中,某些情况下又必须知道T的具体类型特征,才能作出特殊的处理。例如对char型的数组元素累加如果最终返回的也是char型的话,很可能越界,因为char只占8位,范围很小。我们可以为T的trait创建一个模板AccumulationTraits。具体代码如下:
- //accum1.hpp:累加算法模板:实现为函数模板,引入了trait。用数组首部指针及尾部后面的一个指针作参数
- #ifndef ACCUM_HPP
- #define ACCUM_HPP
- #include "accumtraits.hpp"
- #include <iostream>
- template<typename T>
- inline typename AccumulationTraits<T>::AccT accum(T const* beg,T const* end){
- //返回值类型是要操作的元素类型T的trait
- typedef typename AccumulationTraits<T>::AccT AccT;
- AccT total=AccumulationTraits<T>::zero(); //返回具体类型的缺省值
- while(beg!=end){ //作累加运算
- total+=*beg;
- ++beg;
- }
- return total; //返回累加的值
- }
- #endif
- //accumtraits.hpp:累加算法模板的trait
- #ifndef ACCUMTRAITS_HPP
- #define ACCUMTRAITS_HPP
- template<typename T>
- class AccumulationTraits; //只有声明
- //各个特化的定义
- template<>
- class AccumulationTraits<char>{ //把具体类型char映射到int,累加后就返回int
- public:
- typedef int AccT; //统一的类型别名,表示返回类型
- static AccT zero(){ //关联一个缺省值,是累加时的初始缺省值
- return 0;
- }
- };
- template<>
- class AccumulationTraits<short>{ //把具体类型short映射到累加后的返回类型int
- public:
- typedef int AccT;
- static AccT zero(){ //没有直接在类内部定义static变量并提供缺省值,而是使用了函数
- //因为类内部只能对整型和枚举类型的static变量进行初始化
- //其他类型的必须类内部声明,在外部进行初始化
- return 0;
- }
- };
- template<>
- class AccumulationTraits<int>{
- public:
- typedef long AccT;
- static AccT zero(){
- return 0;
- }
- };
- template<>
- class AccumulationTraits<unsigned int>{
- public:
- typedef unsigned long AccT;
- static AccT zero(){
- return 0;
- }
- };
- template<>
- class AccumulationTraits<float>{
- public:
- typedef double AccT;
- static AccT zero(){
- return 0;
- }
- };
- //...
- #endif
- //accum1test.cpp:使用累加算法的客户端代码
- #include "accum1.hpp"
- #include <iostream>
- int main(){
- int num[]={1,2,3,4,5}; //整型数组
- std::cout<<"the average value of the integer values is "
- <<accum(&num[0],&num[5])/5<<'/n'; //输出平均值
- char name[]="templates"; //创建字符值数组
- int length=sizeof(name)-1;
- //输出平均的字符值,返回的是int型,不会越界
- std::cout<<"the average value of the characters in /""
- <<name<<"/" is "<<accum(&name[0],&name[length])/length<<'/n';
- return 0;
- }
注意trait模板本身只是一个声明,并不提供定义,因为它并不知道参数T具体是什么类型。trait的定义由针对各个具体类型的特化来提供。trait依赖于原来模板的主参数T,因为它表示的是T的特征信息。这里使用函数zero()为每个具体类型还关联了一个缺省值,用来作为累加的初始值。为什么不直接关联为静态变量呢?比如static AccT const zero=0。这主要是因为在类内部只能对整型和枚举类型的static变量进行初始化,其他类型的必须在类内部声明,在外部进行初始化。这里对char型数组元素进行累加时,返回int型,这样就避免了会产生越界的情况。
总结出trait模板技术的核心思想: 把模板参数T的具体特征信息抽象成一个独立的模板,通过特化为具体的类型并为该类型关联统一的别名,我们就可以在模板中引用这个别名,以获得T的具体特征信息。注意一个模板参数可能有多种特征,每一个trait都可以抽象成一个trait模板。可见这里特化是获得具体差异行为的关键。由于在模板中类型T是抽象的,不能获得它的具体特征,我们通过对T的特征进行抽离,并特化为具体的类型,才能获得类型的具体特征。从这可以看出我们还有一种实现方案,那就是直接特化模板accum,即针对char型进行一个特化来进行累加。但这样特化版本中又要重写基本模板中那些相同的代码逻辑(比如进行累加的while循环),而实际上我们需要特化的只是类型的特征信息。
在设计层面上,特化与模板的意图正好相反。模板是泛型代码,代表各个类型之间的共性,而特化则表示各个类型之间的差异。我们可以结合多态来深刻地把握这些设计思想。从一般意义上讲,polymorphism是指具有多种形态或行为,它能够根据单一的标记来关联不同的特定行为。可见条件语句if/else也可以看作是一种多态,它根据标记的不同状态值来选择执行不同的分支代码(代表不同的行为)。多态在不同的程序设计范型有不同的表现。
(1)面向过程的程序设计:多态通过条件语句if/else来实现。这样多态其实成了最基本的程序逻辑结构。我们知道顺序语句和条件语句是最基本的逻辑结构,switch语句本身就是if/else的变体,循环语句相当于有一个goto语句的if/else。这种多态可以称为OP多态,它最大优点就是效率高,只有一个跳转语句,不需要额外的开销。最大缺点就难以扩展,很难应对变化。当有新的行为时,就要修改原来的代码,在if/else中再增加一个分支,然后重新编译代码。它只是一种低层次的多态,需要程序员人工增加代码,判断标记的值。
(2)面向对象程序设计:多态通过虚函数机制,用继承的方式来实现。这里的设计思想就是抽离类型之间的共性,把它们放在基类中,而具体的差异性则放到子类中。我们使用基类指针或引用作为单一的标记,它会自动的绑定到子类对象上,以获得不同的行为。函数重载也可以看作是一种多态,函数名作为单一的标记,我们通过不同的参数类型来调用不同的重载版本,从而获得不同的多态行为。这种多态称为OO多态,它的优点就是自动化,易扩展,提高了复用程度。它不需要程序员人工干预,因为动态绑定是自动进行的。当需要新的行为时,从基类继承一个新的子类即可,不需要修改原来的代码,系统易维护,也易扩展。缺点就是降低了效率,当纵向的继承体系比较深时,要创建大量的对象,虚函数一般也很少能够被内联,这会使内存使用量大幅增加。OO多态是一种高层次的多态,耦合性比OP多态低,但纵向的继承体系仍然有一定的耦合性。
(3)泛型程序设计:多态通过模板来实现。这里的设计思想就是不需要抽离类型之间的共性,而是直接对类型进行参数化,把它设计成模板,以表示共性。类型之间的差异通过特化来实现。编译器会根据类型参数(相当于单一的标记)自动决定是从模板产生实例,还是调用特化的实例。这种多态称为GP多态,它是横向的,代表共性的模板与代表差异性的特化在同一层次上,它们之间是相互独立的,因此它的耦合性更低,性能也更好。由于GP本身也支持继承和重载,因此可以看出它是一种更高层次的多态,而用模板来做设计甚至比面向对象设计还强大,因为模板本身也支持面向对象的继承机制,它在面向对象层次上还作了一层更高的抽象(对类进行抽象)。GP多态还具有更好的健壮性,因为它在编译期就进行检查。当然,GP代码比较难调试,这主要由于 编译器支持得不好。
2、用模板参数来传递多种trait。 前面我们在accum中通过组合的方式使用它的trait模板。我们也可直接给accum模板增加一个模板参数用来传递trait类型,并指定一个缺省实参为AccumulationTraits<T>,这样可以适应有多种trait的情况。由于函数模板并不能指定缺省模板实参(其实现在许多编译器都支持这个非标准特性),我们把accum实现为一个类模板。算法作为一个函数来使用时应该会更自然一点,因此可以再用一个函数模板来包装这个类模板,使之变成一个函数模板。如下:
- //accum2.hpp:累加算法模板:实现为类模板,用模板参数来传递trait
- //可用一个内联函数模板作为包装器来包装这个类模板实现
- #ifndef ACCUM_HPP
- #define ACCUM_HPP
- #include "accumtraits.hpp"
- template<typename T,typename AT=AccumulationTraits<T> >
- class Accum{ //实现为类模板,模板参数AT代表要使用的trait,并有一个缺省实参
- public:
- static typename AT::AccT accum(T const* beg,T const* end){
- typename AT::AccT total=AT::zero(); //获取缺省值
- while(beg != end){ //进行累加
- total+=*beg;
- ++beg;
- }
- return total; //返回累加的值
- }
- };
- //用内联的函数模板来包装,对默认的trait,使用一个独立的重载版本
- template<typename T>
- inline typename AccumulationTraits<T>::AccT accum(T const* beg,T const* end){
- return Accum<T>::accum(beg,end);
- }
- template<typename T,typename Traits>
- inline
- typename Traits::AccT accum(T const* beg,T const* end){
- return Accum<T,Traits>::accum(beg,end);
- }
- #endif
使模板参数来传递trait的一个最大好处是当有多种trait时,我们可以为第2个模板参数指定需要的各种trait。这里还使用了所谓的内联包装函数技术 。当我们实现了一个函数(模板),但接口比较难用时,比如这里是类模板,用户即使是使用默认的AccumumationTrait<T>,也要显式指定第一个实参T,不好用。我们可以用一个包装函数来包装它,使其接口变得对用户非常简单友好,为了避免包装带来的性能损失,要把包装函数(模板)声明为内联,编译器通常会直接调用位于内联函数里面的那个函数。这样,使用默认trait时客户端代码accum1test.cpp不需要做任何修改。
3、policy模板技术: 与trait模板技术的思想类似,只不过是对模板代码中的算法策略进行抽离。因为模板代码中对不同的具体类型可能某一部分代码逻辑(即算法策略)会不一样(比如对int是累加,对char则是连接)。policy模板就代表了这些算法策略。它不需要使用特化,policy只需重新实现这个与原模板中的代码不同的具体算法策略即可。
上面是对类型的不同trait产生的差异。实际上对不同的trait,其算法策略(policy)也可能有不同的差异。比如我们对char型元素的数组,不用累加策略,而是用连接的策略。我们还可以把accum看作是一般的数组元素累积性函数,既可以累加,也可以累乘、连接等。一种方法是我们可以直接对accum函数模板的不同具体类型提供特化,重写各自的代码逻辑。但实际上,这时我们需要变化的只有total+=*beg那一条语句,因此我们可以使用policy模板技术,为模板的不同policy创建独立的模板。这里我们把policy实现为具有一个成员函数模板的普通类(当然policy也可以直接实现为模板)。对累加策略为SumPolicy,对累乘策略为MultPolicy等。代码如下:
- //policies1.hpp:累加元素模板的不同policy实现:实现为含有成员函数模板的普通类
- #ifndef POLICIES_HPP
- #define POLICIES_HPP
- class SumPolicy{ //累加的policy
- public:
- template<typename T1,typename T2>
- static void accumulate(T1& total,T2 const& value){
- total+=value; //作累加
- }
- };
- class MultPolicy{ //累乘的policy
- public:
- template<typename T1,typename T2>
- static void accumulate(T1& total,T2 const& value){
- total*=value;
- }
- };
- //其他各种policy
- //......
- #endif
引入了policy后,把累加算法实现为类模板,如下:
- //accum3.hpp:累加算法模板,引入了作为普通类的policy,默认是采用SumPolicy
- #ifndef ACCUM_HPP
- #define ACCUM_HPP
- #include "accumtraits.hpp"
- #include "policies1.hpp"
- template<typename T,typename Policy=SumPolicy,typename Traits=AccumulationTraits<T> >
- class Accum{ //累加算法实现为类模板,默认采用SumPolicy
- public:
- typedef typename Traits::AccT AccT;
- static AccT accum(T const* beg,T const* end){
- AccT total=Traits::zero(); //获取缺省值
- while(beg !=end){ //作累积运算
- Policy::accumulate(total,*beg); //使用给定的算法策略来进行累积
- ++beg;
- }
- return total; //返回累积起来的值
- }
- };
- #endif
当policy为普通类时,这里用一个类型模板参数来传递不同的policy,缺省的policy为SumPolicy。客户端使用Accum<int>::accum(&num[0],&num[5])这样的形式来对int型数组元素进行累加。注意当trait使用默认的AccummulationTrait<T>时,累乘策略MultPolicy实际上就不能用在这里了。因为初始值为0,那累乘的结果最终总是0,可见policy与trait是有联系的。当然我们也可以换一种方法来实现,即直接让accum函数增加一个形参T val,用val来指定运算的初始值。实际上,C++标准库函数accumulate()就是把这个初值作为第3个实参。
4、模板化的policy。 上面的policy实现为具有一个成员函数模板的普通类,这可以看出,其实policy可以直接实现为一个模板。这时在accum算法中就要用模板模板参数来传递policy了。代码如下:
- //policies2.hpp:把各个policy实现为类模板
- #ifndef POLICIES_HPP
- #define POLICIES_HPP
- template<typename T1,typename T2>
- class SumPolicy{
- public:
- static void accumulate(T1& total,T2 const& value){
- total+=value;
- }
- };
- //...
- #endif
- //accum4.hpp:累加算法模板,引入了作为类模板的policy,默认是采用SumPolicy
- #ifndef ACCUM_HPP
- #define ACCUM_HPP
- #include "accumtraits.hpp"
- #include "policies2.hpp"
- template<typename T,
- template<typename,typename> class Policy=SumPolicy,
- typename Traits=AccumulationTraits<T> >
- class Accum{ //累加算法实现为类模板,默认采用模板SumPolicy
- public:
- typedef typename Traits::AccT AccT; //获取返回类型,它是T的trait
- static AccT accum(T const* beg,T const* end){
- AccT total=Traits::zero(); //获取缺省值
- while(beg !=end){ //作累积运算
- Policy<AccT,T>::accumulate(total,*beg); //使用给定的算法策略来进行累积
- ++beg;
- }
- return total; //返回累积起来的值
- }
- };
- #endif
trait模板与policy模板技术的比较:
(1)trait注重于类型,policy更注重于行为。
(2)trait可以不通过模板参数来传递,它表示的类型通常具有自然的缺省值(如int型为0),它依赖于一个或多个主参数,它 一般用模板来实现。
(3)policy可以用普通类来实现,也可以用类模板来实现,一般通过模板参数来传递。它并不需要类型有缺省值,缺省值通常是在policy中的成员函数中用一个独立的参数来传递。它通常并不直接依赖于模板参数。
一般在模板中指定两个模板参数来传递trait和policy。而policy的种类更多,使用更频繁,因此通常代表policy的模板参数在代表trait的模板参数前面。
标准库中的std::iterator_traits<T>是一个trait,可通过iterator_traits<T>::value_ type来引用T表示的具体类型。其实现也是用特化来获取各个具体的类型,有全局特化也有局部物化,如指针类型,引用类型等就只能通过局部特化为T*,T&来实现。
============================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4487724
C++模板中的类型参数T是抽象的,我们并不能在模板内部直接获得它的具体特征。类型萃取(抽取)技术就是要抽取类型的一些具体特征(trait),比如它是哪种具体类型,它是引用类型,内建类型,还是类类型等。可见,类型萃取技术其实就是trait模板技术的具体体现。获取类型的具体特征在Java、C#等语言中也称为反射(reflection),C++中通过模板技术也可以实现一定的反射行为。
类型信息是编译期的实体,现在要针对类型来进行编程,这其实就是模板元编程的一个方面。我们平常使用的if/else,while,for等基本的逻辑结构都是运行期的行为,在面向类型的编程中并不能使用,这就需要用到一些特殊的模板技术。实现类型萃取要用到的基本思想一个是特化,一个就是用typedef来携带类型信息。实际上,我们在用模板做设计时,一般建议在模板定义内部,为模板的每个类型参数提供typedef定义,这样在泛型代码中可以很容易地访问或抽取这些类型。
在C和C++中,普通的函数可以称为值函数,它们接受的参数是某些值,返回的结果也是值。而所谓的类型函数接受的实参是类型,返回的是被抽取出来的类型或常量值等(即用typedef定义的类型别名,一般不同的具体类型都定义统一的别名)。如类模板就是类型函数,sizeof是内建的类型函数,返回给定类型实参的大小。在类型编程中,很多地方都要用到sizeof。
下面演示一些有用的类型萃取实现,这些都是类型函数。
(1)确定某个类型是否为类类型(即class,struct,union): IsClassT<T>。
- //isclasst.hpp:辨别类类型(class,struct,union)
- #ifndef IS_CLASST_HPP
- #define IS_CLASST_HPP
- template<typename T>
- class IsClassT{ //确定某个类型是否为类类型
- private:
- typedef char One;
- typedef struct{
- char a[2];
- } Two;
- template<typename C>
- static One test(int C::*); //C是类类型时使用这个版本,函数参数是一个C的成员指针
- template<typename C>
- static Two test(...); //C是非类类型时使用这个版本
- public:
- enum { Yes=sizeof(test<T>(0))==1 }; //是类类型则Yes为1,不是类类型时Yes为0
- enum { No=!Yes };
- };
- #endif
- //isclassttest.cpp:测试IsClassT的实现
- #include <iostream>
- #include "isclasst.hpp"
- template<typename T>
- void check(){ //检查T是否是类类型:以模板实参方式传递类型
- if(IsClassT<T>::Yes){
- std::cout<<" IsClassT(flag="<<IsClassT<T>::Yes<<")"<<std::endl;
- }else{
- std::cout<<" !IsClassT(flag="<<IsClassT<T>::Yes<<")"<<std::endl;
- }
- }
- template<typename T>
- void checkT(T& a){ //检查T是否是类类型:以函数调用实参方式传递类型
- check<T>();
- }
- class MyClass{ //类类型
- };
- struct MyStruct{ //类类型
- };
- union MyUnion{ //类类型
- };
- void myfunc(){ //非类类型
- }
- enum E{ e1 }e; //非类类型
- int main(){
- std::cout<<"int: ";
- check<int>(); //非类类型
- std::cout<<"MyClass: ";
- check<MyClass>(); //类类型
- std::cout<<"MyStruct: ";
- MyStruct s;
- checkT(s); //类类型
- std::cout<<"MyUnion: ";
- check<MyUnion>(); //类类型
- std::cout<<"enum: ";
- checkT(e); //非类类型
- std::cout<<"myfunc():";
- checkT(myfunc); //非类类型
- return 0;
- }
IsClassT的实现使用了“替换并非错误(SFINAE)“原则。即用模板实参替换模板参数时如果失败(创建出无效的类型),则不一定会出错,因为如果其他的重载版本演绎成功的话,代码仍然会有效。这里使用了对类类型特有的一种构造,即成员指针int (C::*)(...)作为test成员函数模板的形参,...表示成员函数的形参个数不定。当C是类类型时,这个构造有效,返回的char型占1个字节,sizeof的值等于1,Yes就会为1。注意size运算不需要函数的定义,这里的test成员函数模板无需定义实现,只需声明即可。当C是非类类型时,成员指针C::*构造无效,但并不会出错,因为对test的另外一个重载版本,非类类型C是有效的,这时test<T>(0)调用的就是另外那个重载版本,返回的针对char[2]型的结构体占2个字节,sizeof的值等于1,Yes就会为0。
(2)去掉类型中多余的&、const(或volatile)限定符: TypeOp<T>。用于避免在函数模板实参演绎时出现int&&或int const const之类的非法类型。我们知道在C++中int& &(指向引用的引用),int const const之类的式子是不合法的,平时我们一般不会写这样的式子。但是在编写函数模板时,稍不留神就会使模板参数演绎出这样的类型来(C++标准有一个修正允许只在模板实参演绎时出现这样的类型,这时看成是与int&或int const等价,但是很多编译器并不支持这个修正)。看下面的例子:
- //typeoptest.cpp:模板实参演绎错误(出现int& &)的测试
- #include <iostream>
- #include "typeop.hpp"
- /*
- template<typename T>
- void apply(typename TypeOp<T>::RefT arg,void (*func)(T)){
- func(arg);
- }
- */
- template<typename T>
- void apply(T& arg,void(*func)(T)){
- func(arg);
- }
- void print(int a){ //类型为void(int)
- std::cout<<a<<std::endl;
- }
- void incr(int& a){ //类型为void(int&)
- ++a;
- }
- int main(){
- int x=7;
- apply(x,print);
- apply(x,incr);
- return 0;
- }
设实参argument为A,形参parameter为P。在调用apply(x,print)中,对x有A=int,P=T&是引用类型,&可不参与匹配,演绎出T=int;对print有A=void(int)是函数类型,P=void(*)(T)是非引用类型,A会发生退化转型,转型为指针类型void(*)(int),演绎出T=int。两个结论一致,得出T=int。在调用apply(x,incr)中,对incr有A=void(int&),P=void(*)(T),同样退化转型,演绎出T=int&。但是这样x的形参T&就变成了int& &了,不合法。我们可以创建一个类型函数TypeOp<T>来去掉多余的&或const修饰符。如下:
- //typeop.hpp:根据传进去的类型,获取真正需要的类型
- //可去掉类型中多余的&、const,例如避免在函数模板实参演绎时出现int& &或
- //int const const之类的非法类型
- #ifndef TYPEOP_HPP
- #define TYPEOP_HPP
- template<typename T>
- class TypeOp{ //基本模板
- public:
- typedef T ArgT; //参数类型:调用时的参数类型为T
- typedef T BareT; //裸类型:无引用符&,无限定符const
- typedef T const ConstT;
- typedef T& RefT;
- typedef T& RefBareT;
- typedef T const& RefConstT;
- };
- template<typename T>
- class TypeOp<T const>{ //针对const类型的局部特化
- public:
- typedef T const ArgT; //调用时的参数类型:有限定符const
- typedef T BareT;
- typedef T const ConstT;
- typedef T const& RefT;
- typedef T& RefBareT;
- typedef T const& RefConstT;
- };
- template<typename T>
- class TypeOp<T&>{ //针对引用类型的局部特化
- public:
- typedef T & ArgT; //调用时的参数类型:有限定符&
- typedef typename TypeOp<T>::BareT BareT;
- typedef T const ConstT;
- typedef T & RefT;
- typedef typename TypeOp<T>::BareT & RefBareT;
- typedef T const & RefConstT;
- };
- template<>
- class TypeOp<void>{ //针对void的全局特化,因为指向void的引用或const类型不允许
- public:
- typedef void ArgT; //调用时的参数类型:为void
- typedef void BareT;
- typedef void const ConstT;
- typedef void RefT;
- typedef void RefBareT;
- typedef void RefConstT;
- };
- #endif
TypeOp<T>中,对const类型和引用类型进行了局部特化,由于指向void的引用是不允许的,因此针对void类型有一个全局特化。ArgT表示传进来的实参类型,BareT表示实参对应的裸类型(即去掉&或const限定符后的类型)。若函数模板中形参需要是引用类型或const类型,可以把形参声明为TypeOp<T>::RefT或TypeOp<T>::ConstT类型,它就可以进行安全的实参演绎。当然这时T不可演绎,因此必须要有其他的某个形参能演绎出T来(参看注释掉的apply的实现)。
删除typeoptest.cpp中原来的apply函数模板,换成被注释掉的那个apply实现。这里T位于受限名称TypeOp<T>::RefT中,因此这个参数中T不可演绎,但是apply的第2个参数可以演绎出T,然后就可以用演绎出来的结果生成第1个参数的实际类型。基本模板TypeOp<T>代表了传进来的是非引用、非const的类型,因此其ArgT和BareT相同。当传进来的是引用类型时,例如这里是int&(因为第2个参数已经演绎出T=int&),如果是原来的T&,就会变成非法的int& &,现在的TypeOp<T>::RefT就变成了TypeOp<int&>::RefT,这会调用特化版本TypeOp<T&>,其内部T变成了int(去掉了一个&),真正的引用类型RefT就是int&,因此TypeOp<T>::RefT最终结果是int&,而不是int& &。对于传const类型的参数,可以类似分析。注意若演绎出T是void,则从void的全局特化中可以看出其引用类型TypeOp<void>::RefT仍然是void,避免了出现类似void&的无效类型。
(3)从多个类型中选择一个类型: IfThenElse<bool,T1,T2>。根据bool值来选择类型T1或T2。这可用于类型提升、选择返回类型、对类型添加修饰符(如&,const,这可使传值变成传引用)、对类型进行一些基本的操作等。
- //ifthenelse.hpp:根据bool值来选择类型T1或T2
- #ifndef IFTHENELSE_HPP
- #define IFTHENELSE_HPP
- template<bool C,typename Ta,typename Tb>
- class IfThenElse; //基本模板,根据第1个实参来决定是选择第2个实参还是第3个实参
- template<typename Ta,typename Tb>
- class IfThenElse<true,Ta,Tb>{ //局部特化:true的话选择第2个实参
- public:
- typedef Ta ResultT;
- };
- template<typename Ta,typename Tb>
- class IfThenElse<false,Ta,Tb>{ //局部特化,false的话选择第3个参数
- public:
- typedef Tb ResultT;
- };
- #endif
(4)类型提升: Promotion<T1,T2>。可以找出两个类型中更强大的类型,或变成另外一个更强大的类型。一般根据类型所占字节的大小来提升,要用到sizeof运算符。
- //promote1.hpp
- #ifndef PROMOTION_HPP
- #define PROMOTION_HPP
- #include "ifthenelse.hpp"
- template<typename T1,typename T2>
- class Promotion{ //类型提升的基本模板
- public:
- typedef typename
- IfThenElse< (sizeof(T1)>sizeof(T2)),
- T1,
- typename IfThenElse<(sizeof(T1)<sizeof(T2)),T2,void>::ResultT
- >::ResultT ResultT;
- };
- template<typename T>
- class Promotion<T,T>{ //针对两个相同类型的局部特化
- public:
- typedef T ResultT;
- };
- //对基本类型和枚举类型:可能要提升为其他的更强大的类型,
- //这需要另外的特化实现,为方便用户使用,把它们定义成宏
- //把T1,T2提升为Tr
- #define MK_PROMOTION(T1,T2,Tr) /
- template<> class Promotion<T1,T2>{ /
- public: /
- typedef Tr ResultT; /
- }; /
- //......
- //接着可以定义其他的提升规则:比如容器内的元素类型提升
- template<typename T1,typename T2>
- class Promotion<Array<T1>,Array<T2> >{ //容器内的元素类型提升
- public:
- typedef Array<typename Promotion<T1,T2>::ResultT> ResultT;
- };
- template<typename T>
- class Promotion<Array<T>,Array<T> >{ //容器内的元素类型相同时的提升
- public:
- typedef Array<typename Promotion<T,T>::ResultT> ResultT;
- };
- #endif
基本模板中是把两种类型T1,T2提升为其中大的一种类型。T1>T2则提升为T1,T1<T2则提升为T2,否则sizeof(T1)==sizeof(T2),若T1与T2不是同种类型,则会提升为void类型。若T1与T2是同一种类型,则会调用特化版本,直接提升所期望的类型。MK_PROMOTION(T1,T2,Tr)宏是把T1和T2提升为第三种类型Tr,这一般用于内建类型之间的提升,比如把bool和char的混合运算提升为int类型。这里也可以不用宏,而是直接用模板来实现。但是用宏并结合模板来实现代码会更简洁,而且接口也更简洁。注意宏在预处理阶段展开,而模板在编译阶段展开,因此这里的MK_PROMOTION(T1,T2,Tr)实现是可行的。这里还特化了其他的一些提升规则,比如对容器类型Array<T>,对容器内的元素类型进行提升。这时我们还必须提供最后那个针对容器内的元素类型相同的特化。有可能你会认为当容器内的元素类型相同时,会调用Promotion<T,T>这个特化,但实际上Promotion<T,T>与Promotion<Array<T>,Array<T> >的特化程度是一样的,这会产生特化调用的二义性。因此我们必须提供了一个更加特殊化的特化版本Promotion<Array<T>,Array<T> >,这时编译器就会选择调用这个版本。
上面这些类型函数都是用来确定具体的类型属性(trait),没有policy。下面我们为trait引入policy选择功能,上面的类型函数称为property trait,而下面的这些类型函数则可以称为policy trait。
(5)根据不同类型来选择是传值还是传const引用: RParam<T>。比如对类类型使用传const引用的策略,对非类类型使用传值的策略,而对某些可能“传值时性能更好”的类类型,我们可以通过特化来指定它们为传值。我们知道对类类型传值的话,会有昂贵的拷贝构造函数调用开销,特别对于容器类型来说就更耗费资源了。在使用时,把函数的形参声明为RParam<T>::Type类型,就可以自动根据类型T的特征来选择是传值还是传引用。RParam<T>的实现及测试代码如下:
- //rparam.hpp:传值还是传引用的自动选择实现
- #ifndef RPARAM_HPP
- #define RPARAM_HPP
- #include <complex>
- #include "ifthenelse.hpp"
- #include "isclasst.hpp"
- template<typename T>
- class RParam{ //基本模板:非类类型传值,类类型传引用
- public:
- typedef typename IfThenElse<IsClassT<T>::No,
- T,
- T const&>::ResultT Type;
- };
- template<>
- class RParam<std::complex<int> >{ //针对类类型std::complex<int>的特化:让其传值而不是传引用
- public:
- typedef std::complex<int> Type;
- };
- //...
- #endif
- //rparamtest.cpp:测试RParam<T>的实现
- #include "rparam.hpp"
- #include <complex>
- template<typename T1,typename T2>
- void foo_core(typename RParam<T1>::Type p1,
- typename RParam<T2>::Type p2){ //根据实参类型自动决定传值还是传引用
- //...
- }
- template<typename T1,typename T2>
- inline void foo(T1 const& p1,T2 const& p2){ //包装函数:定义为内联函数
- foo_core<T1,T2>(p1,p2);
- }
- class MyClass{
- public:
- MyClass(){ }
- MyClass(MyClass const& rhs){
- std::cout<<"MyClass copy constructor called/n";
- }
- };
- int main(){
- std::complex<int> mc1;
- MyClass mc2;
- foo(mc1,mc2); //第1个参数应该是传值,第2个参数应该是传引用
- //结果是不会调用MyClass的拷贝构造函数
- return 0;
- }
基本模板中,对非类类型传值,对类类型传const类型的引用。针对某些需要特殊对待的类类型,例如假定类类型std::complex<T>传值会比传引用更高效,因此我们就要提供特化来让其传值而不是传引用。由于传值还是传引用是在使用的函数参数中进行的,因此policy的自动选择实际上是在客户端的函数代码中完成。foo_core(p1,p2)是测试的函数,但我们知道RParam<T>::Type并不能用来演绎模板参数T,因此要利用包装函数技术把它包装在内联的foo函数中,这时foo的参数T1和T2就可以演绎了。
还要其他的很多策略。比如对不大于2个指针大小的类型传值,对其他类型则传const引用,而对容器类型的对象,即使它小于2个指针的大小,我们也让它始终传引用,以避免昂贵的拷贝构造函数调用,等等。这种策略实现如下:
- //rparam2.hpp:第二种策略的实现,对大于2个指针大小的类型传值,其他类型传引用
- #ifndef RPARAM_HPP
- #define RPARAM_HPP
- #include <vector>
- template<typename T>
- class RParam{ //基本模板:大于2个指针大小的类型传值,其他类型传引用
- public:
- typedef typename IfThenElse<sizeof(T)<=2*sizeof(void*),
- T,
- T const&>::ResultT Type;
- };
- template<typename T>
- class RParam<std::vector<T> >{ //针对容器类型的特化:让容器类型始终传引用
- public:
- typedef std::vector<T> const& Type;
- };
- //...
- #endif
(6)根据不同类型来选择是拷贝、交换、还是移动: CSMtraits<T>。比如对类类型可以选择非位元的拷贝、交换还是移动,对非类类型执行位元拷贝或移动等。通过特化来实现不同的策略。这里还使用了继承,把实现都放在基类BitOrClassCSM<T,bool>中,让CSMtraits<T>继承自它。对拷贝、交换还是移动能够进行选择主要是基于性能的考虑。有些类型(比如容器类型,智能指针类型),可能交换或移动对象的内容会比直接调用对象的拷贝构造函数更高效,又比如对非类类型,直接的内存位元拷贝可能更高效,而且也比较安全。实际上在C++标准库中对容器类型的对象,通常是不允许拷贝的(这涉及大量的拷贝构造函数调用),而是只能交换或移动容器中存放的元素。CSMtraits<T>的实现如下:
- //csm.hpp:从拷贝copy、交换swap、移动move中自动地选择出最佳的操作,来操作某一特定类型的元素
- #ifndef BIT_OR_CLASS_CSM_HPP
- #define BIT_OR_CLASS_CSM_HPP
- #include <new>
- #include <cassert>
- #include <cstddef>
- #include <cstring>
- #include "rparam.hpp"
- template<typename T,bool Bitwise>
- class BitOrClassCSM; //基本模板
- //用于对象安全拷贝(非位元拷贝)的局部特化
- template<typename T>
- class BitOrClassCSM<T,false>{
- public:
- static void copy(typename RParam<T>::Type src,T* dst){
- //把其中一项拷贝给所对应的另一项
- *dst=src;
- }
- static void copy_n(T const* dst,std::size_t n){
- //把其中n项拷贝给其他n项
- for(std::size_t k=0;k<n;++k){
- dst[k]=src[k];
- }
- }
- static void copy_init(typename RParam<T>::Type src,void *dst){
- //拷贝一项到未进行初始化的存储空间
- ::new(dst) T(src);
- }
- static void copy_init_n(T const* src,void* dst,std::size_t n){
- //拷贝n项到未进行初始化的存储空间
- for(std::size_t k=0;k<n;++k){
- ::new((void*)((char*)dst+k)) T(src[k]);
- }
- }
- static void swap(T* a,T* b){
- //交换其中两项
- T tmp(*a);
- *a=*b;
- *b=tmp;
- }
- static void swap_n(T* a,T* b,std::size_t n){
- //交换n项
- for(std::size_t k=0;k<n;++k){
- T tmp(a[k]);
- a[k]=b[k];
- b[k]=tmp;
- }
- }
- static void move(T* src,T* dst){
- //移动一项到另一项所在的位置
- assert(src!=dst);
- *dst=*src;
- src->~T();
- }
- static void move_n(T* src,T* dst,std::size_t n){
- //移动n项到另n项所在的位置
- assert(src!=dst);
- for(std::size k=0;k<n;++k){
- dst[k]=src[k];
- src[k].~T();
- }
- }
- static void move_init(T* src,void* dst){
- //移动一项到未初始化的存储空间
- assert(src!=dst);
- ::new(dst) T(*src);
- src->~T();
- }
- static void move_init_n(T const* src,void* dst,std::size_t n){
- //移动n项到未初始化的存储空间
- assert(src!=dst);
- for(std::size_t k=0;k<n;++k){
- ::new((void*)((char*)dst+k)) T(src[k]);
- src[k].~T();
- }
- }
- };
- //针对更快的对象位元拷贝而实现的局部特化
- template<typename T>
- class BitOrClassCSM<T,true> : public BitOrClassCSM<T,false>{
- public:
- static void copy_n(T const* src,T* dst,std::size_t n){
- //拷贝n项到其他的对象
- std::memcpy((void*)dst,(void*)src,n);
- }
- static void copy_init_n(T const* src,void* dst,std::size_t n){
- //拷贝n项到未初始化的存储空间
- std::memcpy(dst,(void*)src,n);
- }
- static void move_n(T* src,T* dst,std::size_t n){
- //移动n项到其他对象的n项
- assert(src!=dst);
- std::memcpy((void*)dst,(void*)src,n);
- }
- static void move_init_n(T const* src,void* dst,std::size_t n){
- //移动n项到未初始化的存储空间
- assert(src!=dst);
- std::memcpy(dst,(void*)src,n);
- }
- };
- //根据“是类类型还是非类型“的策略来实现拷贝
- template <typename T>
- class CSMtraits : public BitOrClassCSM<T,IsClassT<T>::No >{
- };
- #endif
这里对非类类型使用位元拷贝,对类类型使用对象安全拷贝(非位元拷贝)。位元拷贝的特化BitOrClassCSM<T,true>继承了非位元拷贝的特化BitOrClassCSM<T,false>,这样就会隐藏父类中签名相同的函数版本,在应用时就会使用自己的实现版本。
===============================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4503870
模板表示类的集合,让模板继承一个类与面向对象编程中的继承并没有本质的差别。但是在模板中使用继承有一些特别的地方,比如基类可以依赖于模板参数(例如继承B<T>,这称为依赖型基类)、甚至模板参数直接可以作为基类,这些所谓的参数化继承,再结合多重继承等C++中特有的继承机制,我们可以发挥模板和继承各自的优势,产生出很多的有趣的技术。
1、命名模板参数。 C++中当多个模板参数有缺省实参时,若需要显式指定其中的某个实参,则其前面的所有缺省实参都要给出。比如BreadSlicer<T1,T2,T3,T4>,T1-T4的缺省实参分别为DefaultArg1-DefaultArg4,若T3要显式给定实参Custom,则必须这样写BreadSlicer<DefaultArg1,DefaultArg2,Custom>,即T1,T2的缺省实参也必须要显式地写出来,这非常麻烦。如果我们能这样写BreakSlicer<T3=Custom>,让其他的参数自动使用缺省实参,那就简洁多了。这时T1-T4就不仅仅是参数的占位符了,它相当于给参数取了一个名字,有了具体的意义,直接给名字赋一个实参,其他参数就会自动采用默认值。实际上,在Python语言中就有这种机制。比如定义函数def parrot(state='a stiff',action='voom')后,就可以像parrot(action='VOOM')这样来调用函数。但是C++并不支持这种语法。通过使用模板和多重继承,我们可以模拟出这种机制来。我们为每个模板参数设计一个辅助的类模板,通过这个类模板来指定需要的实参即可,比如BreadSlicer<Arg3_is<Custom> >,显式指定第三个实参为类Custom,其他参数就自动使用缺省实参,并且无需再写出来了。这里模板Arg3_is相当于给第3个参数T3命了名,用这个名字来指定实参,其他参数就可以自动使用缺省的实参,而无需再显式写出,大大地方便了使用。注意现在我们传过去的实参是Arg3_is<Custom>,因此我们还需要剥离Arg3_is,以获得真正的实参Custom。
命名模板实参的基本实现思想:把缺省类型DefaultArg1-DefaultArg4封装到一个基类DefaultArgs中,用typedef给它们命名好名A1-A4。用Argn_is<T>来为模板的第n个参数指定实参T(n=1~4),Argn_is<T>继承了DefaultArgs,并把传进来的实参T用typedef命名为相应的An,以覆盖父类的相应缺省实参。模板中参数T1-T4的缺省实参都变成统一的DefaultArgs。类型选择器ArgsSelector<T1,T2,T3,T4>用来剥离Argn_is以获得其真正的实参T,它只要多重继承T1-T4即可。例如当传过来的T3是Arg3_is<Custom>,由于ArgsSelector继承了Arg3_is<Custom>,它能看到Arg3_is<Custom>中的A3,通过A3就可引用Custom。最后,我们在模板中组合ArgsSelector就可以通过A1-A4来引用各个实参了。代码如下:
- //nametmpl.hpp:命名模板参数的例子
- #ifndef BREAD_SLICER_HPP
- #define BREAD_SLICER_HPP
- class DefaultArgs{ //封装模板的各个缺省实参
- public:
- typedef DefaultArg1 A1; //第1个模板参数的缺省实参
- typedef DefaultArg2 A2; //第2个模板参数的缺省实参
- typedef DefaultArg3 A3; //第3个模板参数的缺省实参
- typedef DefaultArg4 A4; //第4个模板参数的缺省实参
- };
- class DefaultTypeArgs : virtual public DefaultArgs{ //统一的缺省实参类型
- //...
- };
- template<typename T>
- class Arg1_is : virtual public DefaultArgs{
- public:
- typedef T A1; //用传进来的实参T覆盖第1个模板参数的缺省实参DefaultArg1
- };
- template<typename T>
- class Arg2_is : virtual public DefaultArgs{
- //在模板中使用下面的别名A2来引用对应位置处的实参,可以看出实参若没有显式给出,
- //则引用的是封装在基类中的缺省实参,若通过本类显式给出实参T,则引用的是
- //实际给出的实参
- public:
- typedef T A2;
- };
- template<typename T>
- class Arg3_is : virtual public DefaultArgs{
- public:
- typedef T A3;
- };
- template<typename T>
- class Arg4_is : virtual public DefaultArgs{
- public:
- typedef T A4;
- };
- template<tyepname Arg,int D>
- class ArgsDiscriminator : public Arg{ //实参鉴别模板:通过它使实参选择器可以多次继承同一个基类Arg
- };
- template<typename T1,typename T2,
- typename T3,typename T4>
- class ArgsSelector : public ArgsDiscriminator<T1,1>,
- public ArgsDiscriminator<T2,2>,
- public ArgsDiscriminator<T3,3>,
- public ArgsDiscriminator<T4,4>{ //实参选择器:用来获取真正的实参
- };
- //使用了“命名模板参数”后的模板实现
- template<typename T1=DefaultTypeArgs,
- typename T2=DefaultTypeArgs,
- typename T3=DefaultTypeArgs,
- typename T4=DefaultTypeArgs>
- class BreadSlicer{
- typedef ArgsSelector<T1,T2,T3,T4> TypeArgs;
- //使用TypeArgs::A1、TypeArgs::A2等来引用传起来的各个类型实参
- public:
- void print(){
- TypeArgs::A3::doPrint(); //调用第三个类型实参中的doPrint()方法
- }
- //...
- };
- #endif
从代码中可以看出,现在模板BreadSlicer中要使用实参类型时,要通过ArgsSelector的A1-A4来引用。比如使用BreadSlicer<Arg3_is<Custom> >时,有T1=Arg3_is<Custom>,ArgsSelector通过ArgsDiscriminator<T1,1>间接地继承了Arg3_is<Custom>,它就能看到这个基类中的A3,而A3就是显式指定的Custom了。其他三个参数A1,A2,A4没有通过显式指定来覆盖,则使用的仍然是基类DefaultArgs中的缺省类型。还有几个要注意的地方:
(1)我们并不没有让ArgsSelector<T1,T2,T3,T4>直接继承T1-T4,而是通过ArgsDiscriminator<T,D>来间接继承T1-T4。因为如果直接继承T1-T4的话,当使用BreadSlicer<>时,T1-T4全部变成了缺省的DefaultTypeArgs,这样从4个完全相同的基类继承会导致编译错误。现在继承了ArgsDiscriminator<T1,1>-ArgsDiscriminator<T2,4>(它们直接继承了T1-T4),这4个类是不同的基类,因此没有问题。
(2)T1-T4的缺省实参并没有指定为统一的DefaultArgs,而它的一个直接子类DefaultTypeArgs,这可以让我们根据需要来增加一些不同的行为。
(3)因为ArgsSelector<T1,T2,T3,T4>通过ArgsDiscriminator<T,D>间接地继承了T1-T4,使用时T1-T4指定为Argn_is<T>或者使用默认的DefaultTypeArgs,而DefaultTypeArgs及所有Argn_is都继承自DefaultArgs,这有可能会使ArgsSelector多次继承DefaultArgs,因此DefaultTypeArgs及所有Argn_is都使用虚继承来避免在ArgSelector中产生多份DefaultArgs子对象。
(4)虽然继承层次比较深,但对于那些包含虚基类的辅助命名模板,我们自始至终都没有对它们进行实例化,因此不存在性能或内存耗费的问题。
2、模板参数为空基类时的优化。 我们知道在C++中只包含类型成员、非虚成员函数和静态数据成员的类称为空类,C++中空类大小不能为0,一般会插入一个char型,即大小为1字节。注意当类有非静态数据成员、虚函数或虚基类时,在运行期就要耗费内存(比如有虚函数表vtable)。编译器一般实现了空基类优化技术(EBCO),即当空类作为基类时,子类对象中的基类子对象会优化掉,大小变为0(而不是1个字节)。但我们要注意,当类间接地多次继承同一个基类,导致对象中有同一基类的多个子对象时(即它们分配在同一地址空间上),就不会被优化掉了。
在模板中,模板实参有可能是空类,这会导致模板中该实参类型的成员变量白白地多占据了1个字节的空间。若已知模板实参T必是类类型,且模板中有T类型的成员,也有其他的非空类型A的成员,我们就可以对两个成员进行压缩存储,设计一个压缩存储模板CompressedPair<T,A>并继承T,那个A类型的成员直接为模板的一个成员,而把那个T型的成员存储在压缩模板的基类子对象中,这样当T为空基类时就会被优化掉。代码如下:
- //compressedpair:压缩存储模板,对模板中两个成员变量压缩存储的实现
- #ifndef COMPRESSED_PAIR_HPP
- #define COMPRESSED_PAIR_HPP
- template<typename T1,typename T2>
- class CompressedPair : private T1{ //对两个成员变量的压缩存储,第1个成员变量的T!类型可能为空类
- private:
- T2 member2;
- public:
- //默认构造函数
- CompressedPair():T1(),member2(){ }
- //构造函数:第1个成员变量为T1型,作为本类对象的基子对象部分
- CompressedPair(T1 const& member1,T2 const& m)
- :T1(member1),member2(m){
- }
- //通过first()来获取第1个成员变量(即基类子对象部分)
- T1 const& first() const{
- return (T1 const&)*this;
- }
- T1& first(){ //非const版本
- return this->member2;
- }
- //通过second()来获取第2成员变量
- T2 const& second() const{
- return this->member2;
- }
- T2& second(){ //非const版本
- return this->member2;
- }
- };
- #endif
- //myclass.hpp:使用了压缩存储后的MyClass类实现
- #ifndef MYCLASS_HPP
- #define MYCLASS_HPP
- #include "compressedpair.hpp"
- template<typename T>
- class MyClass{
- private:
- //有两个成员,一个依赖于T,一个不依赖于T
- //T info; //可能为空,为空时不能优化
- //int* storage;
- CompressedPair<T,int*> info_and_storage; //现在可以优化了
- //通过info_and_storage.first()来获得T型成员,info_and_b.second()来获得int*型成员
- public:
- MyClass():info_and_storage(){
- }
- MyClass(T a,int* b):info_and_storage(a,b){
- }
- T getInfo() const{
- return info_and_storage.first();
- }
- int* getStorage() const{
- return info_and_storage.second();
- }
- //...
- };
- #endif
- //myclasstest.cpp:测试MyClass的代码
- #include "myclass.hpp"
- #include <iostream>
- #include <string>
- int main(){
- std::string str="I love you!";
- int val=10;
- int *stor=&val;
- MyClass<std::string> a(str,stor);
- std::cout<<a.getInfo()<<std::endl;
- std::cout<<*a.getStorage()<<'/n';
- return 0;
- }
我们的模板MyClass有两个成员info和storage,info是T型变量(T有可能为空类),storage是一个具体的指针类型的变量,我们可以把这两个变量压缩存储在一个CompressedPair<T,int*>变量中,通过first()或second()函数来获得需要的T型成员和int*型成员。这里T是CompressedPair<T,int*>的基类,当T是空类时,存放了变量info基类子对象部分就会被优化掉,大小变成0。如果不使用压缩存储,则MyClass中有成员T info,即使T是空类,info也要占1个字节。当然这样会使MyClass的实现变得冗长,但是使用了压缩存储后,对使用者而言,许多模板库的性能都得到显著的提高,有时这是值得的。
当然,这样的实现有一个限制,即T必须为类类型,当T可以是非类类型时(比如int),CompressedPair继承T就不行了。另外,当MyClass有两个模板参数T1,T2,有T1 a和T2 b成员变量时,我们也可以对这样的两个变量进行压缩存储。只要修改CompressedPair,让它同时继承T1和T2,并把两个变量都放在基类子对象中,以进行空基类的优化即可。Boost库有一个通用的压缩存储实现boost::compressed_pair。
3、奇异递归模板模式(CRTP): 即派生类将本身作为模板参数传递的给基类。CRTP的一个简单应用是可以记录某个类的对象构造总个数。当然,对对象构造个数进行计数也可以在每个类自己的代码体里面写,只需引入一个整型的静态数据成员,分别在构造函数和析构函数中进行递增和递减操作。有了CRTP,我们就可以把这些功能抽离出来,设计一个用来记录某类型对象构造总个数的通用模板,让需要计数的类继承这个模板,并将本身作为模板参数传给它。代码如下:
- //objectcounter.hpp:对象记数模板
- #ifndef OBJECT_COUNTER_HPP
- #define OBJECT_COUNTER_HPP
- #include <cstddef>
- template<typename T>
- class ObjectCounter{ //用来记录T型对象构造的总个数
- private:
- static std::size_t count; //存在对象的个数
- protected:
- ObjectCounter(){ //缺省构造函数
- ++ObjectCounter<T>::count;
- }
- ObjectCounter(ObjectCounter<T> const& rhs){ //拷贝构造函数
- ++ObjectCounter<T>::count;
- }
- ~ObjectCounter(){ //析构函数
- --ObjectCounter<T>::count;
- }
- public:
- static size_t live(){ //返回存在的对象个数
- return ObjectCounter<T>::count;
- }
- };
- template<typename T>
- std::size_t ObjectCounter<T>::count=0;
- #endif
- //countertest.cpp:对象计数模板的使用测试
- #include "objectcounter.hpp"
- #include <iostream>
- template<typename CharT>
- class MyString : public ObjectCounter<MyString<CharT> >{
- //...
- };
- int main(){
- MyString<char> s1,s2;
- MyString<wchar_t> ws;
- std::cout<<"number of MyString<char>: "
- <<MyString<char>::live()<<std::endl;
- std::cout<<"number of MyString<wchar_t>: "
- <<ws.live()<<std::endl;
- return 0;
- }
====================================================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4508788
(1)计算整数的幂。
- //pow.hpp:计算N的M次幂
- #ifndef POW_HPP
- #define POW_HPP
- template<int N,int M>
- class Pow{ //计算N的M次方的基本模板
- public:
- enum{ result=N*Pow<N,M-1>::result };
- };
- template<int N>
- class Pow<N,0>{ //用于结束递归的局部特化
- public:
- enum{ result=1 };
- };
- #endif
Pow<N,M>用来计算N的M次幂。实例化层次是Pow<N,M>,Pow<N,M-1>,...,Pow<N,0>,共M+1层。我们始终要记住编译器对模板元编程中的实例化层次是有限制的(否则会层次太深的话会耗尽编译器的可用资源),我测试出gcc 4.2.4最多只允许31层的实例化,因此这里如果使用Pow<2,31>,Pow<5,40>等,则编译不会通过。
(2)求整数的平方根(向上取整)。可以用二分查找算法来查找,也可以用普通的从0开始迭代的查找算法。
- //sqrt1.hpp:求整数的平方根(向上取整),用二分查找算法
- #ifndef SQRT_HPP
- #define SQRT_HPP
- #include "ifthenelse.hpp"
- template<int N,int LO=0,int Hi=N>
- class Sqrt{
- public:
- enum{ mid=(LO+HI+1)/2 }; //计算中点
- //执行二分查找:用递归模板,找到结果的类型,然后返回结果
- //enum{ result=(N<mid*mid)? Sqrt<N,LO,mid-1>::result : Sqrt<N,mid,HI>::result };
- typedef typename IfThenElse<(N<mid*mid),
- Sqrt<N,LO,mid-1>,
- Sqrt<N,mid,HI> >::ResultT SubT;
- enum{ result=SubT::result }; //返回最终结果
- };
- template<int N,int M>
- class Sqrt<N,M,M>{ //终止递归的局部特化:适用于LO等于HI
- public:
- enum{ result=M };
- };
- #endif
- //sqrt2.hpp:求整数的平方根(向上取整),用迭代查找算法
- #ifndef SQRT_HPP
- #define SQRT_HPP
- #include "ifthenelse.hpp"
- template<int N>
- class Value{ //包装常量N的模板,使N变成一个类型Value<N>
- public:
- enum{ result=N }; //定义其要返回的结果reuslt
- };
- template<int N,int I=0>
- class Sqrt{ //基本模板,从0开始迭代,以找到N的平方根(向上取整)
- public:
- //执行迭代来找到最终结果的类型:用递归模板
- typedef typename IfThenElse<(I*I<N),
- Sqrt<N,N+1>,
- Value<I> >::ResultT SubT;
- enum{ result=SubT::result; } //返回最终找到的结果
- };
- #endif
在模板元编程中,我们要尽量少用条件运算符?:来执行路径选择,因为这会导致两个分支中的递归模板都会被实例化,产生数量庞大的实例化体。例如使用sqrt1.hpp中注释掉的那句,当实例化Sqrt<N,LO,HI>时,会导致Sqrt<N,LO,mid-1>和Sqrt<N,mid,HI>的完全实例,这样最终的实例化个数大约是N的2倍。我们应该用模板特化来执行路径选择,例如我们使用前面类型萃取技术中介绍的IfThenElse<bool,T1,T2>模板。在它的特化中ResultT只返回其中一个类型(如T1),注意把T1类型typedef成ResultT并不会导致被实例化。当最终查找结果ResultT::resut时,就会实例化ResultT,可见这只实例化了一个IfThenElse中的一个分支。最终的实例化个数趋向于lg(N)。另外,在二分查找的实现中,返回是其中的一个分支类型,最后返回时应该是mid=HI时的Sqrt<N,mid,HI>,因此必须提供一个mid=HI的特化来结束Sqrt模板的递归,否则还会再去实例化Sqrt<N,mid,HI>。而在迭代查找的实现中,当I*I>=N时模板递归结束,这时返回Value<I>分支,用Value<I>::result直接返回计算出的结果,递归结束。因此不需要提供Sqrt的特化来结束递归。
(3)计算向量的点乘。
- //dotproduct.hpp:向量的点乘
- #ifndef DOTPRODUCT_HPP
- #define DOTPRODUCT_HPP
- template<int DIM,typename T>
- class DotProduct{ //基本模板
- public:
- static T result(T* a,T* b){
- //第1个元素的乘积加到剩下的新向量的点乘
- return *a * *b+DotProduct<DIM-1,T>::result(a+1,b+1);
- }
- };
- template<typename T>
- class DotProduct<1,T>{ //作为结束条件的局部特化
- public:
- static T result(T* a,T* b){
- return *a * *b; //最后只有一个元素相乘
- }
- };
- //包装函数:为了使用方便
- template<int DIM,typename T>
- inline T dot_product(T* a,T* b){
- return DotProduct<DIM,T>::result(a,b);
- }
- #endif
- //dotproducttest.cpp:向量点乘的测试
- #include <iostream>
- #include "dotproduct.hpp"
- int main(){
- int a[3]={1,2,3};
- int b[3]={5,6,7};
- std::cout<<"dot_product<3>(a,b) = "<<dot_product<3>(a,b)<<'/n';
- std::cout<<"dot_product<3>(a,a) = "<<dot_product<3>(a,a)<<'/n';
- return 0;
- }
(4)判断一个数是否是素数。
- //isprime.hpp:判断一个数是否是素数
- #ifndef IS_PRIME_HPP
- #define IS_PRIME_HPP
- template<int p,int i>
- class is_prime{ //基本模板:判断p是否是素数
- public:
- enum{ prim=(p==2)||(p%i) && is_prime<(i>2?p:0),i-1>::prim };
- };
- template<>
- class is_prime<0,0>{ //用于结束模板递归的全局特化
- public:
- enum{ prim=1 };
- };
- template<>
- class is_prime<0,1>{ //用于结束模板递归的全局特化
- public:
- enum{ prim=1 };
- };
- #endif
(5)打印连续的素数。
- //primeprinter.hpp:打印连续的素数
- #ifndef PRIME_PRINTER_HPP
- #define PRIME_PRINTER_HPP
- #include <iostream>
- #include "isprime.hpp"
- template<int i>
- class PrimePrinter{ //打印i以下的连续素数
- public:
- enum{ prim=is_prime<i,i-1>::prim };
- static void print(){
- if(prim)
- std::cout<<i<<std::endl;
- PrimePrinter<i-1>::print();
- }
- };
- template<>
- class PrimePrinter<1>{ //用于结束模板递归的全局特化
- public:
- enum{ prim=0 };
- static void print(){
- return;
- }
- };
- #endif
总结出模板元编程的计算完整性:
1)状态变量:也就是模板参数
2)迭代构造(相当于循环语句):通过递归模板
3)路径选择(相当于条件语句):通过使用特化或条件表达式?:
4)整型对象(相当于变量):用enum定义的枚举值
==============================================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4514272
表达式模板技术主要是为了提高存放数值的容器型对象的计算效率。对效率效率要求苛刻(比如大量的数值计算)的计算,使用表达式模板技术可以获得很高的效率,而且客户端的使用代码并没有什么变化,仍然非常紧凑。先看一个简单数组容器模板的实现:
- //sarray.hpp:简单的数组容器类型实现
- #ifndef SARRAY_HPP
- #define SARRAY_HPP
- #include <cstddef>
- #include <cassert>
- template<typename T>
- class SArray{
- private:
- T* storage; //数组元素的存储空间
- size_t storage_size; //元素的个数
- protected:
- void init(){
- for(size_t idx=0;idx<size();++idx)
- storage[idx]=T(); //初始化数组的各个元素
- }
- void copy(SArray<T> const& orig){
- assert(size()==orig.size()); //验证两个数组大小是否一致
- for(size_t idx=0;idx<size();++idx){ //拷贝另一个数组的值
- storage[idx]=orig.storage[idx];
- }
- }
- public:
- explicit SArray(size_t s)
- :storage(new T[s]),storage_size(s){ //创建一个具有初始值大小的数组
- //由s个T类型的元素构成的数组
- init();
- }
- SArray(SArray<T> const& orig)
- :storage(new T[orig.size()]),storage_size(orig.size()){ //拷贝构造函数
- copy(orig);
- }
- ~SArray(){
- delete[] storage; //释放数组的内存空间
- }
- SArray<T>& operator=(SArray<T> const& orig){ //赋值运算符
- if(this!=&orig)
- copy(orig);
- return *this;
- }
- size_t size() const{ //返回数组大小
- return storage_size;
- }
- T& operator[](size_t const idx){ //对SArray变量对象进行下标运算符
- return storage[idx];
- }
- T operator[](size_t const idx) const{ //对Array常量对象(即const对象)进行下标运算符
- return storage[idx];
- }
- SArray<T>& operator+=(SArray<T> const& b);
- SArray<T>& operator*=(SArray<T> const& b);
- SArray<T>& operator*=(T const& s);
- //...
- };
- //对两个SArray求和
- template<typename T>
- SArray<T> operator+(SArray<T> const& a,SArray<T> const& b){
- SArray<T> result(a.size()); //创建了临时的数组
- for(size_t k=0;k<a.size();++k){
- result[k]=a[k]+b[k];
- }
- return result;
- }
- //对两个SArray求积
- template<typename T>
- SArray<T> operator*(SArray<T> const& a,SArray<T> const& b){
- SArray<T> result(a.size());
- for(size_t k=0;k<a.size();++k){
- result[k]=a[k]*b[k];
- }
- return result;
- }
- //让一个SArray乘以一个放大位数(scalar)
- template<typename T>
- SArray<T> operator*(T const& s,SArray<T> const& a){
- SArray<T> result(a.size());
- for(size_t k=0;k<a.size();++k){
- result[k]=s*a[k];
- }
- return result;
- }
- //对SArray和scalar求积
- //对scalar和SArray求和
- //对SArray和scalar求和
- //SArray的自加运算
- template<typename T>
- SArray<T>& SArray<T>::operator+=(SArray<T> const& b){
- for(size_t k=0;k<size();++k){
- (*this)[k]+=b[k];
- }
- return *this;
- }
- //SArray的自乘运算
- template<typename T>
- SArray<T>& SArray<T>::operator*=(SArray<T> const& b){
- for(size_t k=0;k<size();++k){
- (*this)[k]*=b[k];
- }
- return *this;
- }
- //针对放大位数的自乘运算符
- template<typename T>
- SArray<T>& SArray<T>::operator*=(T const& s){
- for(size_t k=0;k<size();++k){
- (*this)[k]*=s;
- }
- return *this;
- }
- //...
- #endif
数组SArray的每个运算符实现中都要创建一个临时数组对象,并扫描一次所有的元素来进行计算,当对复合的表达式进行计算时,效率会非常低。比如我们定义Array<double> x(1000),y(1000); 然后计算表达式x=1.2*x+x*y,这相当于tmp1=1.2*x,tmp2=x*y,tmp3=tmp1+tmp2,x=tmp3,每运算都要创建一个临时数组并扫描一次所有元素(各循环1000次)来计算,还要加上创建和删除tmp对象,最后的赋值又有1000次读和1000次写操作,性能大大降低。对于x=1.2*x+x*y这样的表达式,我们更希望只扫描一次所有的元素,并计算x[i]=1.2*x[i]+x[i]*y[i]。也就是说,进行一次扫描就把表达式的结果计算出来,这样性能会大大提高。我们可以创建表达式模板来实现这一点,把表达式1.2*x+x*y转化成如下类型的对象:
A_Add< double, A_Mult<double,A_Scalar<double>,Array<double> >, A_Mult<double,Array<double>,Array<double> > >
其中double是数组中元素的类型,A_Scalar<T>是对1.2这样的放大倍数的包装,而A_Add<T,OP1,OP2>、A_Mult<T,OP1,OP2>就是所谓的表达式模板,一个是加法表达式,一个是乘法表达式。这里是针对数组运算的表达式,因此T是数组元素的类型,OP1和OP2是数组类型Array<T>或者放大倍数类型A_Scalar<T>。数组类型Array<T>是对使用了表达式模板后的SArray的重新实现,其实Array还可以重用SArray的实现(后面我们会看到)。表达式模板对我们要计算的表达式进行模板化的封装,并最终对数值型的数组元素执行表达式所表示计算。
对数组元素进行计算的表达式模板如下:
- //exprops.hpp:对数组元素进行计算的表达式模板
- #ifndef EXPR_OPS_HPP
- #define EXPR_OPS_HPP
- #include <cstddef>
- #include <cassert>
- #include "exprtrait.hpp"
- //这里的OP1和OP2可以是数组类型Array<T>或放大倍数类型A_Scalar<T>
- template<typename T,typename OP1,typename OP2>
- class A_Add{ //两个数组之和的表达式模板
- private:
- typename A_Traits<OP1>::ExprRef op1; //第1个操作数
- typename A_Traits<OP2>::ExprRef op2; //第2个操作数
- public:
- //构造函数,对指向操作数的引用进行初始化
- A_Add(OP1 const& a,OP2 const& b):op1(a),op2(b){
- }
- T& operator[](size_t const idx){ //在求值时计算和(非const版本)
- return op1[idx]+op2[idx]; //对给定下标处的数组元素做加运算
- }
- T operator[](size_t const idx) const{ //const版本
- return op1[idx]+op2[idx];
- }
- size_t size() const{
- assert(op1.size()==0 || op2.size()==0
- || op1.size()==op2.size());
- return op1.size()!=0?op1.size():op2.size();
- }
- };
- template<typename T,typename OP1,typename OP2>
- class A_Mult{ //两个数组之积的表达式模板
- private:
- typename A_Traits<OP1>::ExprRef op1;
- typename A_Traits<OP2>::ExprRef op2;
- public:
- //构造函数,对指向操作数的引用进行初始化
- A_Mult(OP1 const& a,OP2 const& b):op1(a),op2(b){
- }
- T& operator[](size_t const idx){ //在求值时计算积(非const版本)
- return op1[idx]*op2[idx]; //对给定下标处的数组元素做乘运算
- }
- T operator[](size_t const idx) const{ //const版本
- return op1[idx]*op2[idx];
- }
- size_t size() const{ //size表示最大容量
- assert(op1.size()==0 || op2.size()==0
- || op1.size()==op2.size());
- return op1.size()!=0?op1.size():op2.size();
- }
- };
- #endif
- //exprtrait.hpp:表达式模板参数的trait,根据不同的类型决定传值还是传引用
- #ifndef A_TRAITS_HPP
- #define A_TRAITS_HPP
- #include "exprscalar.hpp"
- template<typename T>
- class A_Traits{ //基本模板
- public:
- typedef T const& ExprRef; //对一般的类型传const引用
- };
- template<typename T>
- class A_Traits<A_Scalar<T> >{ //对scalar(放大倍数)的局部特化
- public:
- typedef A_Scalar<T> ExprRef; //对A_Scalar则传值
- };
- #endif
- //exprscalar.hpp:封装放大倍数(scalar)的类模板
- #ifndef A_SCALAR_HPP
- #define A_SCALAR_HPP
- template<typename T>
- class A_Scalar{
- private:
- T const& s; //scalar的值
- public:
- A_Scalar(T const& v):s(v){
- }
- T& operator[](size_t const idx){ //对A_Scalar变量对象进行下标运算符
- return s; //直接返回倍数值
- }
- T operator[](size_t const idx) const{ //对A_Scalar常量对象进行下标运算符
- return s;
- }
- size_t size() const{
- return 0; //元素个数为0
- }
- };
- #endif
使用了表达模板后的数组类型Array<T>如下:
- //exprarray.hpp: 使用了表达模板后的数组类型Array<T>,及其运算符的实现,
- //第二个参数用于传表达式模板(如A_Add),默认
- //使用的是普通的数组实现SArray
- #ifndef ARRAY_HPP
- #define ARRAY_HPP
- #include <cstddef>
- #include <cassert>
- #include "sarray.hpp"
- #include "exprscalar.hpp"
- #include "exprops.hpp"
- template<typename T,typename Rep=SArray<T> >
- class Array{
- private:
- Rep expr_rep; //持有的对象,可以是底层的SArray数组对象(持有实际的数组数据),
- //也可以是A_Add,A_Mult这样的高层的表达式对象
- public:
- explicit Array(size_t s):expr_rep(s){ //创建具有初始大小的数组
- }
- Array(Rep const& rb):expr_rep(rb){ //根据其他可能的表示来创建数组
- }
- Array<T,Rep>& operator=(Array<T,Rep> const& b){ //针对相同类型的赋值运算符
- assert(size()==b.size());
- for(size_t idx=0;idx<b.size();++idx){
- expr_rep[idx]=b[idx];
- }
- return *this;
- }
- template<typename T2,typename Rep2>
- Array<T,Rep>& operator=(Array<T2,Rep2> const& b){ //针对不同类型的赋值运算符
- assert(size()==b.size());
- for(size_t idx=0;idx<b.size();++idx){
- expr_rep[idx]=b[idx];
- }
- return *this;
- }
- size_t size() const{
- return expr_rep.size();
- }
- T& operator[](size_t const idx){ //对Array变量对象进行下标运算符
- assert(idx<size());
- return expr_rep[idx];
- }
- T operator[](size_t const idx) const{ //对Array常量对象进行下标运算符
- assert(idx<size());
- return expr_rep[idx];
- }
- Rep& rep(){ //返回数组现在所表示的对象
- return expr_rep;
- }
- Rep const& rep() const{
- return expr_rep;
- }
- };
- //实际运算符实现:两个数组相加
- template<typename T,typename R1,typename R2>
- Array<T,A_Add<T,R1,R2> >
- operator+(Array<T,R1> const& a,Array<T,R2> const& b){
- return Array<T,A_Add<T,R1,R2> >(A_Add<T,R1,R2>(a.rep(),b.rep()));
- }
- //两个数组相乘
- template<typename T,typename R1,typename R2>
- Array<T,A_Mult<T,R1,R2> >
- operator*(Array<T,R1> const& a,Array<T,R2> const& b){
- return Array<T,A_Mult<T,R1,R2> >(A_Mult<T,R1,R2>(a.rep(),b.rep()));
- }
- //scalar和数组相乘
- template<typename T,typename R2>
- Array<T,A_Mult<T,A_Scalar<T>,R2> >
- operator*(T const& s,Array<T,R2> const& b){
- return Array<T,A_Mult<T,A_Scalar<T>,R2> >(A_Mult<T,A_Scalar<T>,R2>(A_Scalar<T>(s),b.rep()));
- }
- //数组和scalar相乘
- //scalar和数组相乘
- //数组和scalar相加
- #endif
- //exprtest.cpp:测试代码
- #include "exprarray.hpp"
- int main(){
- Array<double> x(1000),y(1000); //创建两个数组
- x=1.2*x+x*y; //对数组进行计算
- return 0;
- }
这里我们对OP1及OP2封装了一个trait类,如果它是数组类型Array<T>,则使用const引用来创建成员op1(或op2),如果它是放大倍数类型A_Scalar<T>,则直接使用传值的方式来创建成员,这样实现会更高效一点,因为数组类型的引用无需再调用昂贵的拷贝构造函数。表达式模板对给定下标处的数组元素进行这个表达式所表示的实际计算,并通过下标运算符返回计算出来的结果。可见现在表达式(如加A_Add、乘A_Mult,以及把它们结合起来的复杂表达式)的计算最终被映射成对数组元素的实际计算,就像前面说的最终映射成x[i]=1.2*x[i]+x[i]*y[i]。这是关键,在前面的SArray中,每个表达式只是被映射到了数组对象上,其计算调用的是重载运算符,要对整个数组扫描一次。
数组类型Array<T>被设计成既可以使用高层的表达模板,也可以直接使用底层的普通SArray<T>。Rep对象expr_rep持有底层的实际数组数据,rep()返回这个对象,它要么是一个高层的表达式对象,要么是一个底层的SArray数组对象(持有实际的数组数据)。它的赋值运算符、下标运算符可以对这两种情况进行统一的处理。这主要是因为表达式模板的下标运算符和SArray<T>的下标运算符映射到的都是数组元素。现在数组类型的重载运算符就要用表达式模板来实现了。
下面结合测试例子来理解这些运算符。比如x*y,调用operator*的第一个版本,两个操作数x和y的类型都是Array<double,SArray<double> >,R1和R2都使用默认的SArray<double>。调用rep()返回x和y中各自的SArray<double>对象expr_rep,用这两个对象创建一个临时的A_Mult<double,SArray<double>,SArray<double> >类型的表达式对象,然后用这个表达式对象作为Rep对象创建一个要返回的数组类型对象,返回的数组类型为
Array<double,A_Mult<double,SArray<double>,SArray<double> > >
对1.2*x(调用opeator*的第二个版本)类似分析,返回一个Array<double,R3>型的数组对象,这里R3是一个A_Mult的表达式对象。然后两个表达式相加,返回一个Array<double,R4>型的数组对象,这里R4是一个A_Add的表达式对象。最后,调用那个针对不同类型的赋值运算符,把它赋值给x对象。
可见,与原来SArray中的重载运算符相比,现在Array的重载运算符没有进行任何的计算,它只是根据传进来的操作数(数组对象)构造一个与表达式模板相关的新的数组对象并返回,所有的计算都委托给了表达式模板。现在整个表达式x=1.2*x+x*y没有进行任何运算,每一步的运算都只是返回一个新的数组对象,最后是一个数组对象的赋值操作。当调用下标操作x[3]来访问元素时,就会使用表达式模板来进行实际的计算x[3]=1.2*x[3]+x[3]*y[3](跟踪一下下标运算符操作就知道了),以获得我们所需要的值,这是能实现高性能的关键所在。
总结出表达式模板技术的基本思想: 当类需要重载运算符以进行对象的直接运算时,为每个运算符提供一个表达式模板,并把类设计成与表达式模板相关(即把类设计成模板,提供一个模板参数来传递表达式)。重载运算符的实现中并不做实际的运算,而只是根据传进来的操作数构造一个该类型的新对象并返回即可。所有的运算都委托给对应的表达式模板来完成。
===========================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4514808
(1)辨别基本类型:IsFundaT<T>及MK_FUNDA_TYPE(T)。
独特构造:基本类型都是已知的,并且个数有限,因此直接用特化来实现。定义一个基本模板表示非基本类型,然后结合宏MK_FUNDA_TYPE(T)来对各个基本类型提供特化,以表示它们是基本类型,用宏主要是为了使代码更简洁。
- //isfundat.hpp:辨别基本类型
- #ifndef IS_FUNDAT_HPP
- #define IS_FUNDAT_HPP
- template <typename T>
- class IsFundaT { //基本模板:表示非基本类型
- public:
- enum { Yes = 0, No = 1};
- };
- // 用于特化基本类型的宏
- #define MK_FUNDA_TYPE(T) /
- template<> class IsFundaT<T> { /
- public: /
- enum { Yes = 1, No = 0 }; /
- };
- MK_FUNDA_TYPE(void)
- MK_FUNDA_TYPE(bool)
- MK_FUNDA_TYPE(char)
- MK_FUNDA_TYPE(signed char)
- MK_FUNDA_TYPE(unsigned char)
- MK_FUNDA_TYPE(wchar_t)
- MK_FUNDA_TYPE(signed short)
- MK_FUNDA_TYPE(unsigned short)
- MK_FUNDA_TYPE(signed int)
- MK_FUNDA_TYPE(unsigned int)
- MK_FUNDA_TYPE(signed long)
- MK_FUNDA_TYPE(unsigned long)
- #if LONGLONG_EXISTS //如果编译器支持long long类型
- MK_FUNDA_TYPE(signed long long)
- MK_FUNDA_TYPE(unsigned long long)
- #endif
- MK_FUNDA_TYPE(float)
- MK_FUNDA_TYPE(double)
- MK_FUNDA_TYPE(long double)
- #undef MK_FUNDA_TYPE
- #endif
(2)辨别函数类型:IsFunctionT<T>。
独特构造:数组元素的类型不能为void类型、引用类型或函数类型。因此可构造一个测试用的成员函数模板,函数形参为模板参数的数组类型指针,这样数组元素就不能接受函数类型。同时提供该成员另一个重载版本,以应付T是函数类型的情况(SFINAE原则),对有干扰的void类型和引用类型提供特化以表示它们不是函数类型,这样就可以辨别T了。
- //isfunctiont.hpp:辨别函数类型
- #ifndef IS_FUNCTIONT_HPP
- #define IS_FUNCTIONT_HPP
- template<typename T>
- class IsFunctionT {
- private:
- typedef char One;
- typedef struct { char a[2]; } Two;
- template<typename U> static One test(...); //U为函数时使用这个,只需声明,无需定义
- template<typename U> static Two test(U (*)[1]); //U为非函数、非引用及非void类型时使用这个
- public:
- enum { Yes = sizeof(test<T>(0)) == 1 }; //记录测试的结果
- enum { No = !Yes };
- };
- template<typename T>
- class IsFunctionT<T&> { //T是引用类型时会使用这个局部特化,表示它不是函数类型
- public:
- enum { Yes = 0 };
- enum { No = !Yes };
- };
- template<>
- class IsFunctionT<void> { //T是void类型时会使用这个全局特化,表示它不是函数类型
- public:
- enum { Yes = 0 };
- enum { No = !Yes };
- };
- template<>
- class IsFunctionT<void const> { //T是void const类型时会使用这个全局特化
- public:
- enum { Yes = 0 };
- enum { No = !Yes };
- };
- // 对于void volatile和void const volatile类型也是一样
- //...
- #endif
代码中的U (*)[1]是一个指向数组U[1]的指针,U是数组元素的类型。可见这里的U不能为函数、引用及void类型。当U为函数、引用及void以外的其他类型时,sizeof就会使用这个版本的test函数,返回的Two的字节数2,则Yes为0,表示非函数类型。当为引用或void类型时,会使用特化版本,设置Yes为0。当为函数类型时,sizeof中的Test匹配有数组指针的那个Test时不会成功,但它能匹配另一个返回One为1个字节的test版本,根据SFINAE原则,它会使用这个版本的test,Yes设置为1。注意这里test只需声明,无需定义,因为sizeof并不会真正调用并执行该函数,不需要函数代码定义,它只是计算返回类型的字节数。
当然,我们还可以用其他的独特构造来实现,比如只有对函数类型,F&(指向函数类型的引用)才能转化为F*。通过判断能否把一个F&转化为F*,也可辨别出F是否是函数类型。
(3)辨别复合类型:CompoundT<T>::IsPtrT,IsRefT,IsArrayT,IsFuncT,IsPtrMemT,这包括指针类型、引用类型、数组类型、函数类型、成员指针类型。
独特构造:这些类型局部已知,且个数有限,可直接用局部特化来实现。对函数类型则可复用上面的IsFunctionT<T>。
- //compoundt.hpp:辨别复合类型,这包括指针类型、引用类型、数组类型、函数类型、成员指针类型
- #ifndef COMPOUNDT_HPP
- #define COMPOUNDT_HPP
- #include <cstddef>
- #include "isfunctiont.hpp"
- template<typename T>
- class CompoundT { //基本模板
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,
- IsFuncT = IsFunctionT<T>::Yes,
- IsPtrMemT = 0 };
- typedef T BaseT; //T直接依赖的类型
- typedef T BottomT; //去除所有复合依赖后最底层的那个裸类型
- typedef CompoundT<void> ClassT; //T所属的类(用在成员指针类型中)
- };
- template<typename T>
- class CompoundT<T&> { //对引用类型的局部特化
- public:
- enum { IsPtrT = 0, IsRefT = 1, IsArrayT = 0,
- IsFuncT = 0, IsPtrMemT = 0 };
- typedef T BaseT;
- typedef typename CompoundT<T>::BottomT BottomT;
- typedef CompoundT<void> ClassT;
- };
- template<typename T>
- class CompoundT<T*> { //对指针类型的局部特化
- public:
- enum { IsPtrT = 1, IsRefT = 0, IsArrayT = 0,
- IsFuncT = 0, IsPtrMemT = 0 };
- typedef T BaseT;
- typedef typename CompoundT<T>::BottomT BottomT;
- typedef CompoundT<void> ClassT;
- };
- template<typename T, std::size_t N>
- class CompoundT <T[N]> { //对数组类型的局部特化
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 1,
- IsFuncT = 0, IsPtrMemT = 0 };
- typedef T BaseT;
- typedef typename CompoundT<T>::BottomT BottomT;
- typedef CompoundT<void> ClassT;
- };
- template<typename T>
- class CompoundT <T[]> { //对空数组类型的局部特化
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 1,
- IsFuncT = 0, IsPtrMemT = 0 };
- typedef T BaseT;
- typedef typename CompoundT<T>::BottomT BottomT;
- typedef CompoundT<void> ClassT;
- };
- template<typename T, typename C>
- class CompoundT <T C::*> { //对成员指针类型的局部特化
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,
- IsFuncT = 0, IsPtrMemT = 1 };
- typedef T BaseT;
- typedef typename CompoundT<T>::BottomT BottomT;
- typedef C ClassT;
- };
- //
- //下面几个是针对给定参数个数的函数类型局部特化,不提供也没问题,因为上面使用了IsFunctionT<T>
- //提供了还可以访问它们的返回类型和参数类型
- template<typename R>
- class CompoundT<R()> { //对无参数的函数类型的局部特化,R是函数的返回类型
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,
- IsFuncT = 1, IsPtrMemT = 0 };
- typedef R BaseT();
- typedef R BottomT();
- typedef CompoundT<void> ClassT;
- };
- template<typename R, typename P1>
- class CompoundT<R(P1)> { //对单参数的函数类型的局部特化
- public: //R是返回类型,P1是参数类型
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,
- IsFuncT = 1, IsPtrMemT = 0 };
- typedef R BaseT(P1);
- typedef R BottomT(P1);
- typedef CompoundT<void> ClassT;
- };
- template<typename R, typename P1>
- class CompoundT<R(P1, ...)> { //对不少于一个参数的函数类型的局部特化
- public:
- enum { IsPtrT = 0, IsRefT = 0, IsArrayT = 0,
- IsFuncT = 1, IsPtrMemT = 0 };
- typedef R BaseT(P1);
- typedef R BottomT(P1);
- typedef CompoundT<void> ClassT;
- };
- //还可以定义其他给定参数个数的函数类型局部特化
- #endif
这里还对给定参数个数的函数类型定义了局部特化。这些特化不提供也没问题,因为代码中是使用上面的IsFunctionT<T>来辨别函数类型的。但提供这些特化也不冲突,提供它们的好处是我们还可以访问函数类型中的参数类型和返回类型。从中我们也可以看出,不能通过直接的局部特化来辨别一般的函数类型,因为函数的参数个数不确定,这样就需要为每种参数个数的函数类型都提供局部特化,会有无数个局部特化。
(4)辨别类类型(class,struct,union):IsClassT<T>。
独特构造:只有类类型才有成员指针。因此可构造一个测试用的成员函数模板,函数形参为模板参数的一个成员指针,这样模板参数就只能为类类型了。同时提供该成员另一个重载版本,以应付T是非类类型时的情况(SFINAE原则)。
- //isclasst.hpp:辨别类类型(class,struct,union)
- #ifndef IS_CLASST_HPP
- #define IS_CLASST_HPP
- template<typename T>
- class IsClassT{ //确定某个类型是否为类类型
- private:
- typedef char One;
- typedef struct{
- char a[2];
- } Two;
- template<typename C>
- static One test(int C::*); //C是类类型时使用这个版本,函数参数是一个C的成员指针
- template<typename C>
- static Two test(...); //C是非类类型时使用这个版本
- public:
- enum { Yes=sizeof(test<T>(0))==1 }; //是类类型则Yes为1,不是类类型时Yes为0
- enum { No=!Yes };
- };
- #endif
(5)辨别枚举类型。IsEnumT<T>。一种实现是排除上面所有的类型,剩下的就是枚举类型了。我们也可以针对枚举类型的独特构造来提供另一种实现。
独特构造:枚举类型可以隐式转型为整型,这需要排除其他能够转型为整型的基本类型、指针类型、引用类型、成员指针类型的干扰。可用特化,也可用重载解析来实现。定义一个可转型为模板参数的类模板,和一个测试用的函数,为各种受干扰的类型提供该函数的重载版本。通过传递一个可转型的对象给测试函数,转型为模板参数T后,就会运用重载解析来调用相应的重载版本,以辨别出枚举类型。
- //isenumt.hpp:辨别枚举类型,运用类型转换和重载解析来实现
- #ifndef IS_ENUMT_HPP
- #define IS_ENUMT_HPP
- #include "isfundat.hpp"
- #include "compoundt.hpp"
- struct SizeOverOne { char c[2]; }; //用作返回类型
- template<typename T,
- bool convert_possible = !CompoundT<T>::IsFuncT &&
- !CompoundT<T>::IsArrayT>
- class ConsumeUDC { //基本模板:T为非函数类型非数组类型时,ConsumeUDC<T>允许转型为T
- public:
- operator T() const; //转型运算符:转型为T
- };
- template <typename T>
- class ConsumeUDC<T, false> {//对非函数类型非数组类型的特化:ConsumeUDC<T>不允许转型为T
- };
- template <bool convert_possible>
- class ConsumeUDC<void, convert_possible> { //对void类型的特化:不允许转型
- };
- //下面的enum_check函数只需声明,无需定义,因为sizeof运算并不会真正调用该函数
- char enum_check(bool);
- char enum_check(char);
- char enum_check(signed char);
- char enum_check(unsigned char);
- char enum_check(wchar_t);
- char enum_check(signed short);
- char enum_check(unsigned short);
- char enum_check(signed int);
- char enum_check(unsigned int);
- char enum_check(signed long);
- char enum_check(unsigned long);
- #if LONGLONG_EXISTS //如果编译器支持long long类型
- char enum_check(signed long long);
- char enum_check(unsigned long long);
- #endif
- //避免从float到int的意外转型
- char enum_check(float);
- char enum_check(double);
- char enum_check(long double);
- SizeOverOne enum_check(...); //捕获剩余的所有情况
- template<typename T>
- class IsEnumT { //辨别枚举类型
- public:
- enum { Yes = IsFundaT<T>::No &&
- !CompoundT<T>::IsRefT &&
- !CompoundT<T>::IsPtrT &&
- !CompoundT<T>::IsPtrMemT &&
- sizeof(enum_check(ConsumeUDC<T>()))==1 };
- enum { No = !Yes };
- };
- #endif
IsEnumT<T>中,先排除T是基本类型、引用类型、指针类型、成员指针类型的情况。在sizeof运算中,enum_check接受一个ConsumeUDC<T>的临时对象作为参数,由于ConsumeUDC<T>中自定义了转型运算符,因此它会转型为一个T型对象,但T如果是函数类型、数组类型或void类型,则不允许转型(即调用其中的特化版本),这样根据重载解析,enum_check将会选择最后的那个省略号版本,sizeof返回2,Yes设置为0。剩下的只有类类型和枚举类型了。如果T是类类型,则允许转型,转型过来的T型对象只能匹配那个省略号的test版本,Yes被设置为0。注意即使T自定义了一个到整型的转型运算符,也不会再考虑,因为在重载解析的匹配中,只允许有一次的自定义转型。最后,当T是枚举类型时,ConsumeUDC<T>对象自定义转型为T型对象,然后T会类型提升为整型(这个是自动的隐式转型,不是自定义的),从而enum_check匹配一个整型参数的版本,sizeof返回1,Yes设置为1。可见,这个IsEnumT实现比较复杂,这里主要是为了展示各种高级C++语法特性的应用,包括重载解析、模板特化、类型转换(自定义的转型及隐式的类型提升)等。
(5)完整的类型区分实现。
- //typet.hpp:完整的类型区分实现
- #ifndef TYPET_HPP
- #define TYPET_HPP
- #include "isfundat.hpp"
- #include "compoundt.hpp"
- #include "isclasst.hpp"
- #include "isenumt.hpp"
- template <typename T>
- class TypeT { //辨别所有类型的模板
- public:
- enum { IsFundaT = IsFundaT<T>::Yes,
- IsPtrT = CompoundT<T>::IsPtrT,
- IsRefT = CompoundT<T>::IsRefT,
- IsArrayT = CompoundT<T>::IsArrayT,
- IsFuncT = CompoundT<T>::IsFuncT,
- IsPtrMemT = CompoundT<T>::IsPtrMemT,
- IsEnumT = IsEnumT<T>::Yes,
- IsClassT = IsClassT<T>::Yes };
- };
- #endif // TYPET_HPP
(6)测试代码。
- //typestest.cpp:类型区分的测试
- #include "typet.hpp"
- #include <iostream>
- class MyClass { //类类型
- };
- void myfunc(){ //函数类型
- }
- enum E { e1 }; //枚举类型
- template <typename T>
- void check(){ //检查传进来的模板实参类型
- if (TypeT<T>::IsFundaT) {
- std::cout << " IsFundaT ";
- }
- if (TypeT<T>::IsPtrT) {
- std::cout << " IsPtrT ";
- }
- if (TypeT<T>::IsRefT) {
- std::cout << " IsRefT ";
- }
- if (TypeT<T>::IsArrayT) {
- std::cout << " IsArrayT ";
- }
- if (TypeT<T>::IsFuncT) {
- std::cout << " IsFuncT ";
- }
- if (TypeT<T>::IsPtrMemT) {
- std::cout << " IsPtrMemT ";
- }
- if (TypeT<T>::IsEnumT) {
- std::cout << " IsEnumT ";
- }
- if (TypeT<T>::IsClassT) {
- std::cout << " IsClassT ";
- }
- std::cout << std::endl;
- }
- template <typename T>
- void checkT (T a){ //检查传进来的函数调用实参类型
- check<T>();
- //对指针类型,检查它们所依赖的类型
- if (TypeT<T>::IsPtrT || TypeT<T>::IsPtrMemT) {
- check<typename CompoundT<T>::BaseT>(); //BaseT为T所依赖的类型
- }
- }
- int main(){
- std::cout << "int:" << std::endl;
- check<int>();
- std::cout << "int&:" << std::endl;
- check<int&>();
- std::cout << "char[42]:" << std::endl;
- check<char[42]>();
- std::cout << "MyClass:" << std::endl;
- check<MyClass>();
- std::cout << "ptr to enum:" << std::endl;
- E* ptr = 0; //指向枚举类型的指针
- checkT(ptr);
- std::cout << "42:" << std::endl;
- checkT(42);
- std::cout << "myfunc():" << std::endl;
- checkT(myfunc); //myfunc会退化为指针,它所依赖的类型为函数类型void()
- std::cout << "memptr to array:" << std::endl;
- char (MyClass::* memptr) [] = 0; //指向char型空数组的成员指针
- checkT(memptr);
- return 0;
- }
=============================================================================