深入理解C++template的基础知识

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Echoes_smile/article/details/77800812

1.参数化的声明

 C++现今支持两种基本类型的模板:类模板和函数模板(也包括成员模板),

这些模板的声明与普通类与普通函数的声明很相似,唯一的区别就是模板声明需要引入一个参数化的子句,子句的格式大体如下:

template<...parameters list....>

1.1虚成员函数

              成员函数模板不能被声明为虚函数。因为虚函数调用机制的普遍实现都使用了一个大小固定的表,每个虚函数都对应表的一个入口。然而,成员函数模板的实例化个数,要等到整个程序都翻译完毕才能确定,这就和表的大小发生了冲突。

相反,类模板的普通成员可以是虚函数,因为当类被实例化之后,它们的个数是固定的。

eg:

template<typename T>

class Dynamic

{

public:

virtual ~Dynamic();//OK:每个Dynamic之对应一个析构函数


template<typename T2>

virtual void copy(T2 const&);//error:在去顶Dynamic<T>实例的时候,并不知道copy的个数,只有调用copy时才会真正实例化该模板成员函数

}

1.2模板的链接

  • 每个模板都必须有一个名字,而且在它所属的作用域下,该名字是唯一的,除非函数模板可以被重载。特别是,类模板不能和另一个实体共享一个名称。
  • 模板名字是具有链接的,但它们不能具有C链接

eg: extern "C++" template<typename t>

       void normal();//这是缺省情况,上面的链接规范可以不写

extern "C"  template<typename T>

void invalie();//error:模板不能具有C链接

  • 模板通常具有外部链接,唯一的例外是前面有static修饰的名字空间作用于下的函数模板

1.3基本模板:如果模板声明的是一个普通声明,就称其声明的是一个基本模板

eg: template<typename T> class Box;

          template<typename T> void translate(T*);

2.模板参数:

现今存在3种模板参数:

  •  类型参数(使用最多)
  • 非类型参数
  • 模板的模板参数

2.1类型参数:类型参数是通过关键字typename或者class引入的

   在模板声明内部,类型参数的作用类似于typedef名称。

2.2非类型参数

    非类型参数表示的是:在编译期或链接期可以确定的常值。这种参数的类型必须是下面的一种:

  • 整型或枚举类型
  • 指针类型(包括普通对象的指针类型,函数指针类型,指向成员的指针类型)
  • 引用类型(指向对象或者指向函数的引用都是允许的)

    所有 其他类型现今都不允许作为非类型参数使用

或许令你惊讶的是,在某些情况下,非模板参数的声明也可以使用关键字typename

eg:  template<typename T,                                           //类型参数----》简单类型名称

                         typename T::Allocator* Allocator>      //非类型参数----》受限的名称,则必须使用typename进行类型的说明

       class List;

      函数和数组类型也可以被指定为非类型参数,因为它们会隐式转换为指针类型

 最后:非类型模板参数只能是右值:它们不能被取值,也不能被赋值

2.3模板的模板参数

      模板的模板参数是代表类模板的占位符 。它的声明和类模板的声明很类似,但不能使用关键字struct和union。在它们声明的作用域中,模板的模板参数的用法和类模板的用法很相似。

模板的模板参数的参数可以具有缺省模板实参。显然,只有在调用时没有指定该参数的情况下,才会应用缺省模板实参。

eg:  template< template <typename T,

                                              typename A=MyAllocator>  class Container>

        class Adaptation{

                  Container<int> storage;

                                    //隐式等同于Container<int, MyAllocator>

  }

2.4 缺省模板实参

             任何类型的模板实参都可以拥有一个缺省实参,只要该缺省实参能够匹配这个参数就可以。显然,缺省实参布恩那个依赖于自身的参数,但可以依赖于前面的参数

eg: template<typtename T, typename Allocator=allocator<T> >

     class List;//就是说,allocator<T>不能依赖于本身参数Allocator,但是能够依赖于前面参数T

       与缺省的函数调用参数的约束一样,对于任一个模板参数,只有在之后的模板参数都具有缺省实参的前提下,才能具有缺省模板实参。后面的缺省值通常是在同个模板声明中提供的,但也可以在前面的模板声明中提供。

 另外,缺省实参不能重复声明。

3.模板实参:

                :指的是在实例化模板时,用来替换模板参数的值。

我们可以使用以下几种不同的机制来确定这些值:

  • 显式模板实参:紧跟在模板名称后面,在一对尖括号内部的显式模板实参值,所组成的整个实体称为 template-id,  eg: Stack<int>
  • 注入式名称:对于具有模板参数P1,P2,......的类模板X,在它的作用域内,模板名称(即X)等同于template-id:X<P1,P2,.....>
  • 缺省模板实参:如果提供缺省模板实参的化,在类模板的实例 中就可以省略显式模板实参。然而,即便所有的模板参数都具有缺省值,一对尖括号<>也是不能省略的
  • 实参演绎:对于不是显式指定的函数模板实参,可以在函数的调用语句中,根据函数调用实参的类型来演绎出函数模板实参

注意:如果所有的模板实参都可以通过演绎获得,那么在函数模板名称后面就不需要指定尖括号

3.1函数模板实参:可以显式指定,也可以通过实参演绎

eg:

template<typename T>

inline T const& max(T const& a,T const& b){

return a<b?b:a;

}

int main(){

max<double>(1.0,3.0) //显式指定

max(1.0,-3.0);//实参演绎

}

   然而,某些模板实参永远得不到演绎的机会,于是,我们最好把这些实参所对应的参数放在模板参数列表的开始出,从而可以显式指定这些参数,而其他参数仍可以进行实参演绎。

eg: template<typename DstT,typename SrcT>

inline DstT implicit_cast(SrcT const& x)

{

return x;

}

 如果我们调换了例子中模板参数的顺序,也就是说我们将模板写成template<typename SrcT,typename DstT>,那么调用该函数的时候就必须显式指定两个模板实参

    由于函数模板可以被重载,所以对函数模板而言,显式提供的所有实参不足以标识每一个函数。

3.2 类型实参

    模板的类型实参是一些用来指定模板类型参数的值。我们平时使用的大多数类型都可以被用作模板的类型实参,但有两种情况例外:

  • 局部类和局部枚举(即在函数定义内部声明的类型)不能作为模板的类型实参
  • 未命名的class类型或者未命名的枚举类型不能作为模板的类型实参(然而通过typedef声明给出的未命名类和枚举可以作为模板类型实参)

通常而言,尽管其他的类型都可以用作模板实参,但前提是该类型替换模板参数之后获得的构造必须是有效的。

3.3 非类型实参

     非类型实参是那些替换非类型参数的值,这个值必须是以下几种中的一种:

  • 某一个具有正确类型的非类型模板参数
  • 一个编译期整型常量(或枚举值),这只有在参数类型和值的类型能够进行匹配,或者值的类型可以隐式地转换未参数类型(如:一个char值可以作为int参数的实参)的前提下才是合法的。
  • 指针:前面有单目运算符&的外部变量或者函数的名称。对于函数或数组变量,&运算符是可以省略的,这类模板实参可以匹配指针类型的非类型参数
  • 对于引用类型的非类型模板实参,前面没有&运算符的外部变量和外部函数也是可取的
  • 一个指向成员的指针常量,即类似于&C::m的表达式,其中C是一个class类型,m是一个非静态成员,这类实参只能匹配成员指针的非类型参数

注意: 当“指针类型或者引用类型的参数”时,用户定义的类型转换和由派生类到基类的类型转换都不会被考虑

模板实参的一个普遍约束是:在程序创建的时候,编译器或者链接器要能够确定实参的值

有一些常值不能作为有效的非类型实参,这些常值包括:

  • 空指针常量
  • 浮点型值
  • 字符串

3.4 模板的模板实参

    模板的模板实参必须是一个类模板,它本身具有参数,该参数必须精确匹配它“所替换的模板的模板参数”本身的参数。在匹配过程中,“模板的模板实参”的缺省模板实参将不会被考虑(但是如果模板的模板参数具有缺省实参,那么模板的实例化过程是会考虑模板的模板实参的缺省实参的)。

注意:

     从语法上讲,只有关键字class才能被用来声明模板的模板参数,但是这并不意味着只有用关键字class声明的类模板才能作为它的替换实参。实际上,“struct模板”,“union模板”都可以作为模板的模板参数的有效实参,即:对于用关键字class声明的模板类型参数,我们可以用满足约束的任何类型作为它的替换实参

3.5 实参的等价性:当每个对应的实参值都相等时,我们就称这两组模板实参是等价的。对于类型实参,typedef名称并不会对等价性产生影响,也就是说最后比较的还是typedef原本的类型。对于非类型的整型实参,进行比较的是实参的值,至于这些值是如何表达的,也不会产生影响。

注意:从函数模板产生出来的函数一定不会等于普通函数,即便这两个函数具有相同的类型和名称

针对类成员,我们可以引申亮点结论:

  • 从成员函数模板产生的函数永远也不会改写一个虚函数(进一步说明成员函数模板不能是一个虚函数)
  • 从构造函数模板产生的构造函数一定不会是缺省的拷贝构造函数

4. 友元:授予“某个类或者函数访问友元声明所在的类”的权利

 友元类的声明不能是类定义,因此友元类通常都不会出现问题。在引入模板后,友元类声明的唯一变化是:可以命名要给特定的类模板实例为友元

4.1 友元函数:

         可将函数模板的实例声明为友元:在友元函数名称后面加一对尖括号<>,尖括号可以包含模板实参,也可以通过调用参数演绎出实参,如果全部实参都可以通过演绎获得的话,尖括号可为空

注意:我们不能在友元声明中定义一个模板实例。

如果名称后面没有一对尖括号,那么只有在下面两种情况下是合法的:

  • 如果名称不是受限的,那么该名称一定不是(也不能)引用一个模板实例。如果在友元声明的地方,还看不到所匹配的非模板函数,那么这个友元声明就是函数的首次声明。于是,该声明可以是一个定义
  • 如果名称是受限的,那么该名称必须引用一个在此之前声明的函数或函数模板。在匹配过程中,匹配的函数要优于匹配的函数模板。然而这样的声明不能是定义

注意: 如果我们在类模板中定义一个友元函数,那么将会出现一个很有趣的现象。因为对于任何只在模板内部声明的实体,都要等到模板被实例化之后,才会是一个具体的实体,在此之前该实体是不存在的。类模板的友元函数也是如此。尽管这些函数是作为模板的一部分被生成的,但函数本身仍然是普通函数,而不是模板的实例

4.2友元模板:

    通过友元模板,可以让模板的所有实例都成为友元

和普通友元的声明一样,只有在友元模板声明的是一个非受限的函数名称,并且后面没有紧跟尖括号的情况下,该友元模板声明才能成员定义

    友元模板声明的只是基本模板和基本模板的成员。当进行这些声明之后,与该基本模板相对应的模板局部特化和显式特化都会被自动看成友元。


   










展开阅读全文

没有更多推荐了,返回首页