[C++] 模板高级

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZJU_fish1996/article/details/74489345


       reference:《 thinking in C++》


       模板的参数


       (1) 类型

       (2) 编译时常量

       (3) 类模板

       以上几个参数中,前两个参数已经比较熟悉了,关于第三个参数,会在之后给出解释。


        无类型模板参数


        编译时常量,如:

        template<class T, size_t N >


        默认模板参数


       特别注意:只有类模板才支持默认参数,函数模板不支持!但是函数模板可以把类模板作为参数。

       (1) template<class T, size_t N = 100>

       (2) template<class T =  int, size_t N = 100> // 都使用默认参数时,设类为A,调用方式为A<> a;

       (3) template<class T, class Allocator = allocator<T> >

       (4) template<class T> T sum(T* a, T* b, T init = T())


       类模板作为模板的参数


       (1) template<class T, template<class> class seq>

       (2) template<class T, template<class U> class seq>

       (3) template<class T, size_t N, template<class, size_t> class Seq >

       (4)  template<class T,size_t = 10>

              template<class T, template<class, size_t = 10> class Seq> // 重复声明默认参数

       (5) template<class T, template<class U, class = allocator<U> > class seq>


        typename


        作用:

        (1) 等价于声明模板时的class

        (2) 可用于声明类型,详情见下

        假如我们的模板类有一个嵌套类,如果我们不做特别说明,那么编译器会认为这是一个对象(变量),而不会认为它是一个类型,为了告诉编译器这是一个类型,我们可以加上typename关键词。比如模板类为T,嵌套类为A,则写作:

        typename T::A variable;

        以下两种情况不用写typename:

        (1) 类型在基类中声明过

        (2) 已经出现在同一作用域的初始化列表中

        

        成员模板

       (1) 函数作为成员模板 (不能声明为virtual!)


        声明:

        template<typename T> class complex {
        public:
                template<class X> complex(const complex<X>&)


        实现:

        template<typename T>
        template<typename X>
        complex<T>::complex(const complex<X>& c) { ... }

       (2) 类作为成员模板


       声明:

        template<class A>
        class Out {
        public:
                template<class B>
                class In {
                public:
                       void fun();
                }
        };

        实现:

        template<class A>
        template<class B>
        void Out<A>::In<B>::fun() { };


       函数模板调用

        template< typename T> const T& min (const T&a, const T&b) {
              return ( a < b ) ? a : b;
       }


       (1) 带参调用

      int ans = min <int> ( i , j );

       (2) 类型推断

        int ans  = min ( i , j );

      但要注意:

      (1) 类型推断不支持标准转换,如果希望用到,需要指明类型。

      (2) 模板参数作为返回值时,无法使用类型推断,需要指明类型。


        函数模板重载


       如果编译器能够区分,可以重载模板函数。

       使用类型推断时,如果有非模板函数作为最优匹配,就会优先匹配非模板函数。用<>标记会强制匹配模板函数。

       

        以函数模板作为参数


        template <typename T> 
        void f(T*) { }

        template < typename T>
        void g ( void (*pf) (T*))  {  }

        调用方式:

        (1) g<int>(&f<int>)

        (2) g(&f<int>)

        (3) g<int>(&f);

        以上三种方式编译器都能够获取(推断)类型。


        函数模板的半有序


        一般而言,会优先匹配特化程度最高的模板:

        所谓特化程度高,也就是说当一个调用可以匹配多个模板函数的时候,能匹配对象更少而又恰好能匹配当前参数的那个模板是最佳的,如下:

        template<class T> void f(T); //(1)
        template<class T> void f(T*);//(2)
        template<class T> void f(const  T*); //(3)

        对于指向const的指针而言,(3)是最佳匹配,对于普通指针而言,(2)是最佳匹配,(1)是一个通用的匹配。

        这时候存在这么一个问题,如果我们没有办法区分出哪个是特化程度最高的匹配,会发生什么呢?

        事实上在这种不明确的情况下,编译器会报错。


        模板特化


        (1) 显式特化

     

        template<class T>const T& min(const T& a, const T& b) {
                return (a < b) > a : b;
       }

        template<>
        const char* const& min<const char*> (const char* const& a,const char* const& b) {
                return strcmp(a,b) < 0 ? a : b;
        }

        vector类包含了一个bool显式特化,将bool信息用二进制存储:

        template<class T, class Allocator = allocator<T> >
        class vector { ... };

       template<>
        class vector<bool,allocator<bool> > { ... } ;


        (2) 半特化


        也就是只特化一般分参数。

       template<class T,class U> class C {
       public : 
               void f() { cout << "全特化\n"; }
       };

        template<class U> class C<int, U> {
        public:
                void f() { cout << "特化T为int\n"; }
        };

        template<class T> class C<t,double> {
        public:
                void f() { cout << "特化U为double\n"; }
        };
    
        template<class T, class U> class C<T*,U> {
        public:
                void f() { cout << "特化T为指针类型\n"; }
        };

        template<class T,class U> class C<T, U*> {
        public:
                void f() { cout << "特化U为指针类型\n"; } 
        };

        template<class T,class U> class C<T*, U*> {
        public:
                void f() { cout << "特化T,U为指针类型\n"; } 
        };


        但要注意的是,很多半特化可以匹配重叠的参数,可能会引起二义性,导致编译器报错。

        简化的模板编写方式


        我们的模板为了匹配各种各样的类型,通常需要我们手动写很多特化合半特化模板来匹配所有的情况,最终编译器将会在原有代码基础上生成大量的代码,使得代码体积越来越大,这个时候,我们有必要使用void*指针来提供统一接口,并减小最终实际的代码体积。比如对于栈而言,我们提供以下几种:

        template<class T> class Stack {...} // 这是主要的模板

        template<> class Stack<void*> {...}  // 一个void*的全特化

        template<class T> class Stack<T*> {...} //一个其它指针类型的半特化


        第一个主要模板匹配大多数参数,第二个void*匹配大部分指针,第三个半特化主要用于匹配Stack<void*>私有派生的指针类型。


        限定关联、非限定关联、非关联


        模板编译包含两个阶段。

        第一阶段,解析模板定义以及一些不依赖于模板参数的名称;第二阶段,进行模板实例化。

       

        void g() { cout << "global g()" << endl;}
        template<class T> class Y {
        public : 
                void g() { cout << "Y g()" << endl;
                void h() { cout << "Y h()" << endl;
                typedef int E;
        }
 
        typedef double E;
            
       template<class T> void swap(T& t1,T& t2) {...}

       template<class T> class X : public Y<T> {
               E f() {  // E:非关联,所以在第一阶段处理,类型为double
                   g(); // g() : 非关联,所以在第一阶段处理,调用的是全局声明
                   this->h(); // 限定关联,对象为X
                   T t1 = T(), t2 = T(1);  // t1,t2 : 关联
                  cout << t1 << endl; // operator << (cout,t1) 关联
                  swap (t1,t2); // 非限定关联,参数是T,也就引起了swap(int&,int&)实例化
                  std::swap(t1,t2); // 限定非关联
                  return E(t2);
               }
       }


  

      什么是限定与非限定?是否有指明类名前缀。

      什么是关联?在这里指与模板空间匹配。

  

      关联名称: 在实例化时才查找(第二阶段)

       非限定的关联名称 : 普通查找,在定义时进行

       非关联名称 : 模板定义解析时查找(第一阶段)


        模板与友元


        在模板函数中声明友元函数可以有两种写法:

        (1) 友元函数也是模板:

        friend void f<>(const Friendly<T>&);// 在这里<>代表这是一个模板函数

        这时候如果非内联定义需要提前声明友元函数;因为模板本身没有提供,它不支持隐式转换。

        此外,对于每个实例化的模板而言,它所对应的友元函数是唯一的,就是和它类型相同的那个函数。f<int>(const Friendly<T>&)对应Friendly<int>,如果在类中写一个特化的摸板友元函数,它将成为所有实例化的类的友元函数;同样,不依赖T的非模板友元函数也是所有实例化类的友元函数。

        如果希望f的所有特化都成为Friendly特化的友元函数,可以加一个新的模板参数的特化:

         template<class U> friend void f<>(const Friendly<U>&);


        (2) 友元函数是含T的普通函数

        friend void f(const Friendly<T>&);//这时不含有<>
        每实例化一个类模板,就会生成一个Friendly参数的f() 的重载函数,因为不是模板,所以支持隐式转换。

猜你喜欢

转载自blog.csdn.net/ZJU_fish1996/article/details/74489345