C++学习笔记十六-模板和泛型编程(二)

一、类模板成员

      1.模板作用域中模板类型的引用:

         A.在类模板的作用域内部,可以用它的非限定名字引用该类。

         B.通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字 QueueQueue<Type> 缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。因此,复制构造函数定义其实等价于:

     Queue<Type>(const Queue<Type> &Q): head(0), tail(0)
                 { copy_elems(Q); }
     C.编译器不会为类中使用的其他模板的模板形参进行这样的推断,因此,在声明伙伴类 QueueItem 的指针时,必须指定类型形参:
     QueueItem<Type> *head;    // pointer to first element in Queue
 
   2. 类模板成员函数 : 类模板成员函数的定义具有如下形式: 

          A.必须以关键字 template 开关,后接类的模板形参表。

          B.必须指出它是哪个类的成员。

          C.类名必须包含其模板形参。

     从这些规则可以看到,在类外定义的 Queue 类的成员函数的形式应该是:

         template <class T> ret-type Queue<T>::member-name

 

     3.类模板成员函数的实例化:类模板的成员函数本身也是函数模板。像任何其他函数模板一样,需要使用类模板的成员函数产生该成员的实例化。与其他函数模板不同的是,在实例化类模板成员函数的进修,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定。

对象的模板实参能够确定成员函数模板形参,这一事实意味着,调用类模板成员函数比调用类似函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换

Queue qi; // instantiates class Queue
short s = 42;
int i = 42;
// ok: s converted to int and passed to push
qi.push(s); // instantiates Queue::push(const int&)
qi.push(i); // uses Queue::push(const int&)
f(s);       // instantiates f(const short&)
f(i);       // instantiates f(const int&)
 
   4.何时实例化类和成员:类模板的成员函数只有为程序所用才进行实例化。如果某函数从未使用,则不会实例化该成员函数。这一行为意味着,用于实例化模板的类型只需满足实际使用的操作的要求。只接受一个容量形参的顺序容器构造函数就是这样的例子,该构造函数使用元素类型的默认构造函数。如果有一个没有定义默认构造函数的类型,仍然可以定义容器来保存该类型,但是,不能使用只接受一个容量的构造函数。
  
   5.定义模板类型的对象时,该定义导致实例化类模板。定义对象也会实例化用于初始化该对象的任一构造函数,以及该构造函数调用的任意成员:
     // instantiates Queue class and Queue::Queue()
     Queue qs;
     qs.push("hello"); // instantiates Queue::push

Queue 类中的 QueueItem 成员是指针。类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。因此,在创建 Queue 对象进不会实例化 QueueItem 类,相反,在使用诸如 frontpushpop 这样的 Queue 成员时才实例化 QueueItem 类。

 

    6.非类型形参的模板实参:这个模板有两个形参,均为非类型形参。当用户定义 Screen 对象时,必须为每个形参提供常量表达式以供使用。类在默认构造函数中使用这些形参设置默认 Screen 的尺寸。

  像任意类模板一样,使用 Screen 类型时必须显式声明形参值:

    Screen<24,80> hp2621; // screen 24 lines by 80 characters

  非类型模板实参必须是编译时常量表达式。

 

二、类模板中的友元声明:

      1.在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:

         A.普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

         非模板类或非模板函数可以是类模板的友元:

        template <class Type> class Bar {
             // grants access to ordinary, nontemplate class and function
             friend class FooBar;
             friend void fcn();
             // ...
         };

        这个声明是说,FooBar 的成员和 fcn 函数可以访问 Bar 类的任意实例的 private 成员和 protected 成员。

 

         B.类模板或函数模板的友元声明,授予对友元所有实例的访问权。

         友元可以是类模板或函数模板:

     template <class Type> class Bar {
         // grants access to Foo1 or templ_fcn1 parameterized by any type
         template <class T> friend class Foo1;
         template <class T> friend void templ_fcn1(const T&);
         // ...
     };

        这些友元声明使用与类本身不同的类型形参,该类型形参指的是 Foo1temp1_fcn1 的类型形参。在这两种情况下,都将没有数目限制的类和函数设为 Bar 的友元。Foo1 的友元声明是说,Foo1 的友元声明是说,Foo1 的任意实例都可以访问 Bar 的任意实例的私有元素,类似地,temp_fcn1 的任意实例可以访问 Bar 的任意实例。

       这个友元声明在 Bar 与其友元 Foo1temp1_fcn1 的每个实例之间建立了一对多的映射。对 Bar 的每个实例而言,Foo1temp1_fcn1 的所有实例都是友元。

 

         C.只授予对类模板或函数模板的特定实例的访问权的友元声明。

     template <class T> class Foo3;
     template <class T> void templ_fcn3(const T&);
     template <class Type> class Bar {
         // each instantiation of Bar grants access to the
         // version of Foo3 or templ_fcn3 instantiated with the same type
         friend class Foo3<Type>;
         friend void templ_fcn3<Type>(const Type&);
         // ...
     };

        这些友元定义了 Bar 的特定实例与使用同一模板实参的 Foo3temp1_fcn3 的实例之间的友元关系。每个 Bar 实例有一个相关的 Foo3temp1_fcn3 友元:

     Bar<int> bi;    // Foo3<int> and templ_fcn3<int> are friends
     Bar<string> bs; // Foo3<string>, templ_fcn3<string> are friends

 

       D.声明依赖性:

         当授予对给定模板的所有实例的访问权时候,在作用域中不需要存在该类模板或函数模板的声明。实质上,编译器将友元声明也当作类或函数的声明对待。

         想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:

      template <class T> class A;
     template <class T> class B {
     public:
         friend class A<T>;      // ok: A is known to be a template
         friend class C;         // ok: C must be an ordinary, nontemplate class
         template <class S> friend class D; // ok: D is a template
         friend class E<T>;      // error: E wasn't declared as a template
         friend class F<int>;    // error: F wasn't declared as a template
      };

        如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。

 

三、成员模板

      1.任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。定义成员模板模板成员声明看起来像任意模板的声明一样:

     template <class Type> class Queue {
     public:
         // construct a Queue from a pair of iterators on some sequence
         template <class It>
         Queue(It beg, It end):
               head(0), tail(0) { copy_elems(beg, end); }
         // replace current Queue by contents delimited by a pair of iterators
         template <class Iter> void assign(Iter, Iter);
         // rest of Queue class as before
     private:
         // version of copy to be used by assign to copy elements from iterator range
         template <class Iter> void copy_elems(Iter, Iter);
     };

    成员声明的开关是自己的模板形参表。构造函数和 assign 成员各有一个模板类型形参,这些函数使用该类型形参作为其函数形参的类型,它们的函数形参是指明要复制元素范围的迭代器。

     2.在类外部定义成员模板:

当成员模板是类模板的成员时,它的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign 函数定义的形式为

     template <class T> template <class Iter>

第一个模板形参表 template<class T> 是类模板的,第二个模板形参表 template<class Iter> 是成员模板的。

 

     3.成员模板遵循常规访问控制:成员模板遵循与任意其他类成员一样的访问规则。如果成员模板为私有的,则只有该类的成员函数和友元可以使用该成员模板。

     4.成员模板和实例化:与其他成员一样,成员模板只有在程序中使用时才实例化。类模板的成员模板的实例化比类模板的普通成员函数的实例化要复杂一点。成员模板有两种模板形参:由类定义的和由成员模板本身定义的。类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样。这些形参都通过常规模板实参推断而确定。

 

四、类模板的 static 成员

     1.类模板可以像任意其他类一样声明 static 成员。以下代码:

     template <class T> class Foo {
     public:
        static std::size_t count() { return ctr; }
        // other interface members
     private:
        static std::size_t ctr;
        // other implementation members
     };
     // Each object shares the same Foo<int>::ctrand Foo<int>::count members
     Foo<int> fi, fi2, fi3;
     // has static members Foo<string>::ctrand Foo<string>::count
     Foo<string> fs;

    每个实例化表示截然不同的类型,所以给定实例外星人所有对象都共享一个 static 成员。因此,Foo<int> 类型的任意对象共享同一 static 成员 ctrFoo<string> 类型的对象共享另一个不同的 ctr 成员。

 

    2.使用类模板的 static 成员:

      通常,可以通过类类型的对象访问类模板的 static 成员,或者通过使用作用域操作符直接访问成员。当然,当试图通过类使用 static 成员的时候,必须引用实际的实例化:

     Foo<int> fi, fi2;              // instantiates Foo<int> class
     size_t ct = Foo<int>::count(); // instantiates Foo<int>::count
     ct = fi.count();               // ok: uses Foo<int>::count
     ct = fi2.count();              // ok: uses Foo<int>::count
     ct = Foo::count();             // error: which template instantiation?

与任意其他成员函数一样,static 成员函数只有在程序中使用时才进行实例化。

    3.定义 static 成员:

像使用任意其他 static 数据成员一样,必须在类外部出现数据成员的定义。在类模板含有 static 成员的情况下,成员定义必须指出它是类模板的成员:

     template <class T>
     size_t Foo<T>::ctr = 0; // define and initialize ctr

   static 数据成员像定义在类外部的任意其他类成员一样定义,它用关键字 template 开头,后面接着类模板形参表和类名。在这个例子中,static 数据成员的名字以 Foo<T>:: 为前缀,表示成员属于类模板 Foo。

 

 

五、模板特化

   引言:我们并不总是能够写出对所有可能被实例化的类型都最合适的模板。某些情况下,通用模板定义对于某个类型可能是完全错误的,通用模板定义也许不能编译或者做错误的事情;另外一些情况下,可以利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数。

      1.函数模板的特化:模板特化(template specialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:
A.关键字 template 后面接一对空的尖括号(<>);

B.再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;

C.函数形参表;

D.函数体。

     2.当模板形参类型绑定到 const char* 时,compare 函数的特化:

     // special version of compare to handle C-style character strings
     template <>
     int compare<const char*>(const char* const &v1,
                              const char* const &v2)
     {
         return strcmp(v1, v2);
     }

现在,当调用 compare 函数的时候,传给它两个字符指针,编译器将调用特化版本。编译器将为任意其他实参类型(包括普通 char*)调用泛型版本:

 

       2.声明特化:

与任意函数一样,函数模板特化可以声明而无须定义。模板特化声明看起来与定义很像,但省略了函数体:

     // declaration of function template explicit specialization
     template<>
     int compare<const char*>(const char* const&,
                              const char* const&);

这个声明由一个后接返回类型的空模板形参表(template<>),后接一对尖括号中指定的显式模板实参的函数名(可选),以及函数形参表构成。模板特化必须总是包含空模板形参说明符,即 template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显式指定模板实参

     // error: invalid specialization declarations
     // missing template<>
     int compare<const char*>(const char* const&,
                              const char* const&);

     // error: function parameter list missing
     template<> int compare<const char*>;

     // ok: explicit template argument const char* deduced from parameter types
     template<> int compare(const char* const&,
                            const char* const&);

 

       3.函数重载与模板特化:当定义非模板函数的时候,对实参应用常规转换;当特化模板的时候,对实参类型不应用转换。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例

       4.不是总能检测到重复定义:如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件中出现。不能在一些文件中从泛型模板定义实例化一个函数模板,而在其他文件中为同一模板实参集合特化该函数模板。

与其他函数声明一样,应在一个头文件中包含模板特化的声明,然后使用该特化的每个源文件包含该头文件。

六.类模板的特化:

   1.为 C 风格字符串的 Queue 提供正确行为的一种途径,是为 const char* 定义整个类的特化版本:

     /* definition of specialization for const char*
      * this class forwards its work to Queue<string>;
      * the push function translates the const char* parameter to a string
      * the front functions return a string rather than a const char*
      */
     template<> class Queue<const char*> {
     public:
         // no copy control: Synthesized versions work for this class
         // similarly, no need for explicit default constructor either
         void push(const char*);
         void pop()                  {real_queue.pop();}
         bool empty() const          {return real_queue.empty();}
         // Note: return type does not match template parameter type
         std::string front()         {return real_queue.front();}
         const std::string &front() const
                                     {return real_queue.front();}
     private:
         Queue<std::string> real_queue; // forward calls to real_queue
     };

      A.值得注意的是,特化可以定义与模板本身完全不同的成员。如果一个特化无法从模板定义某个成员,该特化类型的对象就不能使用该成员。类模板成员的定义不会用于创建显式特化成员的定义。

      B.类模板特化应该与它所特化的模板定义相同的接口,否则当用户试图使用未定义的成员时会感到奇怪。

   2.类特化定义:在类特化外部定义成员时,成员之前不能加 template<> 标记。

     我们的类只在类的外部定义了一个成员:

     void Queue<const char*>::push(const char* val)
     {
         return real_queue.push(val);
     }
  3.特化成员而不特化类:成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参表开头:
     // push and pop specialized for const char*
     template <>
     void Queue<const char*>::push(const char* const &);
     template <> void Queue<const char*>::pop();
  4.类模板的部分特化:

     如果类模板有一个以上的模板形参,我们也许想要特化某些模板形参而非全部。使用类模板的部分特化可以做到这一点:

     template <class T1, class T2>
     class some_template {
         // ...
     };
     // partial specialization: fixes T2 as int and allows T1 to vary
     template <class T1>
     class some_template<T1, int> {
         // ...
     };

      类模板的部分特化本身也是模板。部分特化的定义看来像模板定义,这种定义以关键字 template 开头,接着是由尖括号(<>)括住的模板形参表。部分特化的模板形参表是对应的类模板定义形参表的子集。some_template 的部分特化只有一个名为 T1 的模板类型形参,第二个模板形参 T2 的实参已知为 int。部分特化的模板形参表只列出未知模板实参的那些形参。

    5.使用类模板的部分特化:

部分特化与对应类模板有相同名字,即这里的 some_template。类模板的名字后面必须接着模板实参列表,前面例子中,模板实参列表是 <T1,int>。因为第一个模板形参的实参值未知,实参列表使用模板形参名 T1 作为占位符,另一个实参是类型 int,为 int 而部分特化模板。

像任何其他类模板一样,部分特化是在程序中使用时隐式实例化:

     some_template<int, string> foo; // uses template
     some_template<string, int> bar; // uses partial specialization

注意第二个变量的类型,形参为 stringintsome_template,既可以从普通类模板定义实例化,也可以从部分特化实例化。为什么选择部分特化来实例化该模板呢?当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。foo 的实例化类型与提供的部分特化不匹配,因此,foo 的类型必然从通用类模板实例化,将 int 绑定到 T1 并将 string 绑定到 T2。部分特化只用于实例化第二个类型为 intsome_template 类型。

     部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。

 

七、重载与函数模板

函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。

当然,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性。

      1.如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下:

         A. 为这个函数名建立候选函数集合,包括:
         与被调用函数名字相同的任意普通函数。
         任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参。
         B.  确定哪些普通函数是可行的(第 7.8.2 节)(如果有可行函数的话)。候选集合中的每个模板实例都 可行的,因为模板实参推断保证函数可以被调用。
         C.  如果需要转换来进行调用,根据转换的种类排列可靠函数,记住,调用模板函数实例所允许的转换是有限的。
         如果只有一个函数可选,就调用这个函数。
         如果调用有二义性,从可行函数集合中去掉所有函数模板实例。
         D.  重新排列去掉函数模板实例的可行函数。
         如果只有一个函数可选,就调用这个函数。
         否则,调用有二义性。

posted @ 2012-07-23 17:11  ForFreeDom  阅读(1235)  评论(0编辑  收藏  举报