终于到模板了,不过,模板这个心里真没底。这个东西应该是c++区别其他c系列语言的杀手锏,c++11更是引进了外部模板来简化了其自身在使用过程中代码扩张,添加了对匿名类和对象的支持,添加变长模板的支持,使其已完全形成了一种新的语言,可以说现在他就是寄生于c++中的另一种语言。而其关于多态的提供了一种继承的多动态所没有的优势。另,c++动多态,是指继承这种多态,其是在运行期来绑定的,而静多态是指通过模板在编译期便绑定的
多态的实质,便是我们可以根据需要对实例化出多种需要。
- c++ 的动多态具有优点
- 能够优雅地处理异类集合
- 可执行代码的大小通常比较小(相比静态多态,因其针对不同类型会生成不同的代码,代码量会增大)
- 可对对代码进行完全编译;因此并不需要发布实现源码。(模板库通常都需要同时发布模板实现的源代码的,难以做到定义和实现的分离)
- c++r 静多态具有的优点
- 可以很容易地实现内建类型的集合。更广义的说,并不需要通过公共类来表达接口的共同性。
- 所生成的代码效率通常都比较高(相比动多态,没有指针的间接调用,且有更多机会使非虚函数内联)
- 对于只提供部分接口的具体类型,如果在应用程序中只是使用到这一部分接口,那么也可以使用该具体类型;而不必在乎该类型是否提供其他部分的接口。
- 所以一般的认为,静多态具有更好的类型安全性,因为静多态在编译期会多所有的绑定操作会进行检查。但在实际应用中,对于看起来相同的接口,如果在它们背后隐藏着一些语文假设的话,那么模板实例化体有时也会导致一些问题。即若 我们用一个类型实例化一个模板,在这模板中用到此类型+运算,但此类型并无此运算方法,此时便出现不匹配的问题,虽这种问题在编译过程中往往就可以解决,但,这种语义不匹配另人很不爽,相反在动态多态中这是很少见的。
- 所以在具体的设计过程中,应该结合二者的优点,以桥模式为主设计需要。
开始吧:
- 函数模板
- inline 和 constexpr 的位置放在模板参数列表之后,返回类型之前。
- 函数模板在c++11中可以有默认值。
template < typename t =int> void func(t a){ cout <<typeid(t).name(); } int main() { func(1); //输出 int }
- 在一些情况下,编译器无法推断出模板函数的参数类型,此时需要使用显示实参。即若我们显示指定了类型,编译器将试图把传入的参数进行指定类型的转换,若转不成功,则编译错误。关于模板函数的参数实例化中,有一点需要注意,便是其也可将模板用于返回类型,但这种方法的确显得笨拙。我们可以通过 auto和尾置返回的方法来解决这个问题。当然在尾置转换中我们可以结合type_traits库中的众多方法,得到我们想要的类型、
- 引用折叠的问题
- 在模板中可能存在引用的折叠问题,即 x& & 、x&& &以及 x& &&均将折叠成 X&,而只有x&& &&折叠为x&&
- 这个问题有两层意思, 一是当一个函数参数是模板类型的一个普通左值引用时,只能传递给他一个左值。二是当一个函数参数是模板类型的一个右值引用时,当我们将一个左值传递给函数的右值引用函数,且此右值引用指向模板类型类型参数时,编译器推断为左值为引用,
- 还需明确指明的是参数是&&的,其虽可既传入左值、也可传入一个右值,但传入的又值,其时便是以值的方式存在的。
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template < typename t =int> void func(t a){ cout <<typeid(t).name(); } template <class m> void cun(m&& a){ cout << is_rvalue_reference<m>::value; cout << is_lvalue_reference<m>::value; cout << is_literal_type<m>::value; } int main() { cun(4);//输出001 }
- 我们可以从此推断出标准库中move的函数的可能实现方法可能是如下所示,其核心即是将一个左值转去除掉引用性,同时对传入右值不予处理。
template<class T> typename remove_reference<T>::type move(T&& a){ return static_cast<typename remove_reference<T>::typename>(a);}
- 由此还引出一个性质便是转发的问题,我们在即在若我们把一个类型传入到一个函数中,如何保持类型不会变化,前面3中只解决了此问题的一半,若参数为&&,传入一个左值,会保留,但传入一个右值,依旧不会保留,此时我们可以利用标准库中std::forward<T>()来进一步保留其中的信息。
- 模板的重载
- 模板函数的重载与前相同,符合函数名相同,参数不同即可
- 若多个函数完全匹配,则按照非模板,特化模板,模板的顺序匹配执行
- 一条经验,对于定义任何函数之前,记得声明所有重载的函数版本。这样便不必担心编译器会出现你预想不到的事情。
- 模板类
- 编译器不能为类推断出类模板的类型,故类模板需要显示实例化。
- 在模板类中定义成员函数,可少写模板参数这个标记,在类外,只个必须,因为类需要。而若成员函数是一个模板函数,此时便需要两个模板参数,前一个是类的,后一个是模板的。这个不好看。
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template <class T> class t{ public: template<class A> void func(A b); }; template<class T> template <class A> void t<T>::func(A b){ cout << "this is in template class template function;"; } int main() { t<int>().func<int>(3);//this is in template class template function;当然在调用函数时,我们亦可不以显示的方式调用,可通过推断即可,但在对于类则必须以显示的方式调用,即使其类模板中含有默认参数,我们依然需要显示,此果可以不指定具体类型,但<〉不可缺 }
- 关于模板类的友元一言以蔽之,若友元是一个模板类A,若一个类,或一个模板类B以A的一个特例为友元,请前置声明A是一个模板类,否则无此需要。
- 关于类的偏特实例化或叫部分实例化,有两个概念,一是针对某种类型进行明确的实例化,另一个针对某种修饰符进行明确实例化,如引用,或指针之类。还可以针对成员函数进行特例化。
- 模板类型别名,在原可以通过typedef的方式指定具体的模板类型,现可以通过using指定一个模板类型别名。
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template <class t ,class u> struct s{}; template <typename t> using p = s<t,t>; // 这个声明只能在全局空间中,不能在函数中。!! int main() { typedef s<int,int> ints; //typdef 可以明确类型,可以在函数中直接声明。 ints a; p<double> b; }
- 范围限定符在编译器中默认其为值,所以如果使用::域限定符时,声明一个类型,在前需要添加typename来标记域解析出的为一个类型,而非数值。
- 模板类中的模板成员函数不能是虚函数,对于类中的静态型,其访问,定义也需要以模板的形式来处理,这个看起来不好看,但意义是一样的。
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template <class t ,class u> struct s{ static const int fd ; }; template<class t,class u> const int s<t,u>::fd =435; template <> const int s<int,float>::fd = 2233;//定义的形式式,在定义特例时,可以省略其中模板参数。但定义形式还是一样的。在函数中定义中特例化亦是如此 int main() { cout << s<int,float>::fd<<endl; s<int,double> d; cout << d.fd; }
- 可变参数模板的问题
- 例子
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template<class t> void func(t a){ cout << a; } template <class t,class ... Args> //定义形式 void func(t&& a,Args&& ... rest ){ cout<<a<<endl; func(rest...); // 调用形式 } int main() { func("nihao",12,.99,5678906545L); }
- 需要注意的是可变形参的使用形式
- 在可变参数模板中的一般的定义要按照递归定义。或者不用到所有具体的参数时,也可直接定义,而勿需递归处理。
- 我们也可将可变参数模板与std::forward<T>来结合将参数不变地传递给其他函数。
- 可变参可用sizeof...运算符来求模板参数个数。
#include <iostream> #include <typeinfo> #include <type_traits> using namespace std; template<class t> void func(t a){ cout << a; } template <class t,class ... Args> void func(t&& a,Args&& ... rest ){ cout<<a<<endl; func(rest...); } template <class ... T> void g (T ... t){ cout << sizeof...(T); } int main() { func("nihao",12,.99,5678906545L); cout << endl; g(1,2,3,4,5,6,7,8);//8 }
- 例子