为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

C++_模板_代码

2011-11-20 10页 doc 61KB 37阅读

用户头像

is_413216

暂无简介

举报
C++_模板_代码模板的魅力 当前,对于多数C++程序员来说,模板常常意味着类型的简单替换。例如: view plaincopy to clipboardprint? template T max(T const & a, T const & b) { return (a>b)?a:b; } template T max(T const & a, T const & b) { return (a>b)?a:b; } 或 std::vector 这样的用法。诚然,这是一种编程...
C++_模板_代码
的魅力 当前,对于多数C++程序员来说,模板常常意味着类型的简单替换。例如: view plaincopy to clipboardprint? template T max(T const & a, T const & b) { return (a>b)?a:b; } template T max(T const & a, T const & b) { return (a>b)?a:b; } 或 std::vector 这样的用法。诚然,这是一种编程模式:一些类、数、代码,宏观上的操作是相同的,不同的仅是被操作的类型,于是把类型特别提出来用一记号表示,并由template关键字声明,成为一通用的类、函数、代码;使用时,只要把特定的类型代替进去即可。在很多支持泛型的语言中,如C#、Java等等,模板就是这样用的。但是,读过《C++ Templates》或《Modern C++ Design》或《C++ Template Metaprogramming》的朋友都知道,C++的模板,包含的内容更多,功能也更为强大。 对于C++模板精髓,我想主要是SFINAE(Substitution failure is not an error)原则。SFINAE原则意味着,函数实例若不能匹配某个函数模板,这不算编译错误,还可以尝试去匹配其它函数模板。这个保护性的原则,提高了函数模板的重载能力,进而使得编译时类型推导成为可能。如下面的代码 view plaincopy to clipboardprint? #include template char check(int C::*); template float check(...); template bool IsClassType() { return sizeof(check(0))==1 ; }; class One; int main() { printf("Is A Class Type %d\n",IsClassType()); printf("Is A Class Type %d\n",IsClassType()); return 0; } #include template char check(int C::*); template float check(...); template bool IsClassType() { return sizeof(check(0))==1 ; }; class One; int main() { printf("Is A Class Type %d\n",IsClassType()); printf("Is A Class Type %d\n",IsClassType()); return 0; } 对于模板template char check(int C::*),只有当C类型为类时,才可能有int C::* 这样的类型(表示一个指针,指向C类的某int型成员),这样才能匹配此模板。而对于template float check(...),可匹配任意类型的T。所以当check(0)不能匹配前者时,它会匹配后者,从而使IsClassType能得到正确的结果。利用SFINAE原则,可以做很多类型推导:判断一个类型是否为某类型或某类类型(如判断T是否为class类型),类型是否相等(T1与T2是否为同一类型),类型选择(根据某类型,选择T1类或T2类),判断两类型是否有继承关系等等。 模板的另一重要特性为静态性。这点是指模板的实例化是编译时进行时,程序运行时,已经是模板实例的结果了。利用这一点,可以优化代码。如上面的程序中,计算IsClassType(),实际上已经在编译时计算完毕了,在程序中,直接替代为true;而不是在程序运行进再根据T的类型,重新计算sizeof(check(0))是否等于1。再看一个例子,这程序显示了模板的计算能力(当然,这些计算都是编译时进行了;程序运行时,就能直接输出结果了;具体原理说明见此): view plaincopy to clipboardprint? #include template class Pow3 { public: enum { result=3*Pow3::result }; }; template<> class Pow3<0> { public: enum { result = 1 }; }; int main() { std::cout << "Pow3<7>::result = " << Pow3<7>::result << '\n'; } #include template class Pow3 { public: enum { result=3*Pow3::result }; }; template<> class Pow3<0> { public: enum { result = 1 }; }; int main() { std::cout << "Pow3<7>::result = " << Pow3<7>::result << '\n'; } 模板的分类与使用 模板分为函数模板和类模板,有着显著的不同!不能因为两者都能进行类型替换(最主要的能力),而认为两者都差不了多少。在细节上,正是这些差异,使我写出模板常常不能顺利地通过编译器编译。 函数模板,一般的形式为: template RetType fun(T t); 而类模板,一般形式为: template class MyClass; 函数模板的模板参数,不能有默认值(目前是这样),而类模板可以有;函数模板可以重载,重载的方式很多;但类模板是不能重载的,只能有一个基本模板。 对于第一个差异,常常出现的问题是,使用多了stl::vector >,也认为函数模板也能带默认的参数。而事实是,目前的标准里不允许这样,虽然编译器实现这个功能并不困难;而对待这样的语法错误,不同的编译器会给出不同的信息,其中VC给的信息非常不助于解决问题----“默认模板参数应该是出现在类模板中”,gcc则直接指出了“函数模板不能有默认参数”。 对于第二个差异,实际上是体现了类模板与函数模板发挥作用的不同方式:函数模板走的是“重载路线”,而类模板走的是“特化路线”。这两种方式所体现的,正是C++模板不同于普通泛型(类型替换)的特别之处。 重载函数模板 函数模板实际表式的是一系列逻辑相似的函数族。例如前面的T max(T const&, T const &)函数模板。当代码上求max(1,2)时,编译器根据参数1,2的类型,推断出T为int,从而产生了一个函数int max(int const &, int const&)。这个过程叫做函数模板的实参演绎。当出现函数模板重载的情况时,如: view plaincopy to clipboardprint? #include template int fun(T) { return 1; } template int fun(T*) { return 2; } int main() { int a=10; printf("%d\n",fun(a)); printf("%d\n",fun(&a)); return 0; } #include template int fun(T) { return 1; } template int fun(T*) { return 2; } int main() { int a=10; printf("%d\n",fun(a)); printf("%d\n",fun(&a)); return 0; } 输出结果为1,2。这个例子表明,对于函数模板,编译器会根据参数的类型,去选择最适合的函数。这个“最适合”往往是对参数限制得最多的一种选择。这也是之前对于类类型的One,check函数模板会被选择为char check(int C::*)的原因。 再举一个详细点的例子:对于自定义的list和vector两种数据结构,两者有基本相同的外部接口;但前者由链表实现,后者由数组实现,能支持随机访问;具体说来,这两者使用的迭代器不同,一个是bi_iterator(双向访问),一个是random_iterator(随机访问)。因此,要对list进行排序,要使用一种支持双向迭代器的link_sort(),而对于vector有更高效的quick_sort(),它仅支持随机访问迭代器。现在希望能统一接口,实现一sort函数,可以接受list和vector两种类型(stl里还有其它类型)的容器。代码如下: view plaincopy to clipboardprint? class bi_iterator; class random_iterator; class list { public: typedef bi_iterator iterator; iterator begin(); iterator end(); }; class vector { public: typedef random_iterator iterator; iterator begin(); iterator end(); }; template void sort(container &c) { typename container::iterator tag; _sort_select(c, tag); } template void _sort_select(container &c, bi_iterator & tag) { link_sort(c.begin(), c.end()); } template void _sort_select(container &c, random_iterator & tag) { quick_sort(c.begin(), c.end()); } class bi_iterator; class random_iterator; class list { public: typedef bi_iterator iterator; iterator begin(); iterator end(); }; class vector { public: typedef random_iterator iterator; iterator begin(); iterator end(); }; template void sort(container &c) { typename container::iterator tag; _sort_select(c, tag); } template void _sort_select(container &c, bi_iterator & tag) { link_sort(c.begin(), c.end()); } template void _sort_select(container &c, random_iterator & tag) { quick_sort(c.begin(), c.end()); } 这同样是函数重载的例子,编译器根据_sort_select()的第二个参数tag,确定调用的是那一个函数模板,再生成相应的函数实例。上面的代码中,tag的值是多少其实并不重要,有用的是tag的类型,编译器据此来判断选择哪一个函数模板。编译后,tag被优化掉,并没有在程序中占用空间。(注意:typename container::iterator tag这一句里的关键字typename是用于强调iterator是模板参数container里的一个类型,避免一些语法上的起义发生。) 当然,为统一接口,还可以给list,vector都添加sort()方法。 特化类模板 特化是指通使用指定的模板实参来特殊化一个类,目标常常是优化基于某些类型的类实现。 例如 view plaincopy to clipboardprint? template // 基本模板 stack { public: void push(T const & t); T pop(); T const & top(); private: T elements[N]; }; template stack // 局部特化 { public: void push(string const & t); string pop(); string const & top(); private: string elements[N]; }; template // 基本模板 stack { public: void push(T const & t); T pop(); T const & top(); private: T elements[N]; }; template stack // 局部特化 { public: void push(string const & t); string pop(); string const & top(); private: string elements[N]; }; stack是一个栈的类模板,T表示元素的类型,N表示栈的大小(整数可以做为模板参数)。对于一个字符串string的栈,我们期待更优化的实现。所以可以把T特化成具体的类型string,重新实现此模板(包括方法和成员)。由于这里只特化了一个模板参数,因此叫局部特化。 前面编译时计算Pow3的例子,也是类模板特化的例子:计算是递归进行的,计算Pow3::result,需要先得到Pow3::result;而最后的边界条件是通过特化得到的,即Pow3<0>::result=1。 下面再看一个特化的例子: view plaincopy to clipboardprint? template // 基本模板 struct IsSameType { enum {result = false}; }; template struct IsSameType //特化为T1=T2=T { enum {result = true}; }; template // 基本模板 struct IsSameType { enum {result = false}; }; template struct IsSameType //特化为T1=T2=T { enum {result = true}; }; 此例中:IsSameType::result 的值表征了T1,T2两个类型是否为同一类型。 如果基本模板的成员不完整,而特化的模板却是完整的类模板,这样又能起另一作用: view plaincopy to clipboardprint? #define StaticAssert(X) \ { \ Assert< (X) > assert; \ } template struct Assert; template<> struct Assert { }; #define StaticAssert(X) \ { \ Assert< (X) > assert; \ } template struct Assert; template<> struct Assert { }; 可以看到,类模板templatestruct Assert只有声明(bool值也可以做为模板参数的实参),没有定义,而struct Assert是有定义的。这就意味着,如果定义变量 Assert v,则会得到一个编译错误。利用这种有缺陷的类模板,我们得到了一个静态断言StaticAssert。组合上面的代码,我们可以写出这样一个max的函数模板: view plaincopy to clipboardprint? template T max(T & t1, T & t2) { StaticAssert( !IsClassType() ) return (t1>t2)?t1:t2; } template T max(T & t1, T & t2) { StaticAssert( !IsClassType() ) return (t1>t2)?t1:t2; } 这个max(),要求T类型不能是类类型。因为若T为类,t1,t2可能是大对象,像代码中那样直接返回大对象是非常低效的。 注意:函数模板也可以特化,这与类模板特化是基本一致的。但在强大的实参演绎机制与重载机制前面,函数模板的特化往往不被提及。
/
本文档为【C++_模板_代码】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索