<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    PS,1880后程序員

    看不完的牙,寫不完的程序,跑不完的步。
    隨筆 - 97, 文章 - 34, 評論 - 10, 引用 - 0
    數據加載中……

    C++ Primer 之 讀書筆記 第十六章 模板和泛型編程

     

    第十六章模板和泛型編程

    C++中模板是泛型編程的基礎。(In C++, templates are the foundation for generic programming.

    16.1 模板定義Template Definitions

    16.1.1 定義函數模板 Defining a Function Template

    函數模板是和類型無關的函數定義,它被當作是公式用來生成特定類型版本的函數。(A function template is a type-independent function that is used as a formula for generating a type-specific version of the function.

    // implement strcmp-like generic compare function

    // returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller

    template <typename T> //模板參數列表template parameter list

    int compare(const T &v1, const T &v2)

    {

        if (v1 < v2) return -1;

        if (v2 < v1) return 1;

        return 0;

    }

    模板參數列表不能為空。(The template parameter list cannot be empty.)這很好理解哈,如果沒有參數,還要模板干嘛。

    類型形參

    上面這個例子,模板形參是類型形參(type parameter)。類型形參代表的是類型,定義的跟在關鍵字class或者typename之后。

    使用函數模板

    編譯器一旦確定了模板的實參,就會實例化模板函數。(Once the compiler determines the actual template argument(s), it instantiates an instance of the function template for us.)編譯器生成并編譯用實參取代對應的模板形參的函數版本。編譯器承擔了為每種類型重寫函數的枯燥的任務。(it generates and compiles a version of the function using those arguments in place of the corresponding template parameters. The compiler takes on the tedium of (re)writing the function for each type we use.

    要明確的是編譯器是在編譯期完成這些任務的。

    int main ()

    {

        // T is int;

        // compiler instantiates int compare(const int&, const int&)

        cout << compare(1, 0) << endl;

        // T is string;

        // compiler instantiates int compare(const string&, const string&)

        string s1 = "hi", s2 = "world";

        cout << compare(s1, s2) << endl;

        return 0;

    }

    16.1.2 定義類模板Defining a Class Template

    template <class Type> class Queue {

     public:

          Queue ();                // default constructor

          Type &front ();          // return element from head of Queue

          const Type &front () const;

          void push (const Type &); // add element to back of Queue

          void pop();              // remove element from head of Queue

          bool empty() const;      // true if no elements in the Queue

     private:

          // ...

     };

    //使用模板

    Queue<int> qi;                 // Queue that holds ints

    Queue< vector<double> > qc;    // Queue that holds vectors of doubles

    Queue<string> qs;              // Queue that holds strings

    16.1.3. 模板形參 Template Parameters

    Glorp就是模板的形參。

    // equivalent template definition

    template <class Glorp>

    int compare(const Glorp &v1, const Glorp &v2)

    {

        if (v1 < v2) return -1;

        if (v2 < v1) return 1;

        return 0;

    }

    模板形參作用域

    模板形參的作用域是從模板形參從聲明之后,直到模板聲明或者定義結束。(The name of a template parameter can be used after it has been declared as a template parameter and until the end of the template declaration or definition.

    注意:模板形參會屏蔽全局的同名的對象,函數或者是類型。

    typedef double T;

    template <class T> T calc(const T &a, const T &b)

    {

         // tmp has the type of the template parameter T

         // not that of the global typedef

         T tmp = a; //T對應的是模板類型形參,而不是double

         // ...

         return tmp;

    }

    模板聲明Template Declarations

    每一個模板類型形參前面都必須以關鍵字class或者typename;每個非類型形參前面必須是類型的名稱。(Each template type parameter must be preceded either by the keyword class or typename; each nontype parameter must be preceded by a type name.

    // declares compare but does not define it

    template <class T> int compare(const T&, const T&) ;

    16.1.4. 模板類型形參 Template Type Parameters

    typename和class的區別

    二者其實是沒有區別的,只是為了更加直觀,才區分是typename環視class。當然,混用也是可以的:

    template <typename T, class U> calc (const T&, const U&);

    在模板定義里定義類型

    關鍵字typename表示size_type是綁定到Parm上的類型,而不是數據成員。

    template <class Parm, class U>

    Parm fcn(Parm* array, U value)

    {

        typename Parm::size_type * p; // ok: declares p to be a pointer

    }

    16.1.5. 非類型模板形參 Nontype Template Parameters

    模板非類型形參是常量。

    // initialize elements of an array to zero

    template <class T, size_t N> void array_init(T (&parm)[N])

    {

        for (size_t i = 0; i != N; ++i) {

            parm[i] = 0;

        }

    }

    //calling

    int x[42];

    double y[10];

    array_init(x); // instantiates array_init(int(&)[42]

    array_init(y); // instantiates array_init(double(&)[10]

    通過引用傳遞數組

    需要參看一下Section7.2.4中“通過引用傳遞數組(Passing an Array by Reference)”。

    這是從Section7.2.4抄過來的一個例子:(嘎嘎,大師就是有先見之明,Section7.2.4就是直接指到這部分的。)

    // ok: parameter is a reference to an array; size of array is fixed

    //arr是一個int類型,長度是10的數組的引用

    void printValues( int (&arr)[10] ) { /* ... */ }

    int main()

    {

        int i = 0, j[2] = {0, 1};

    int k[10] = {0,1,2,3,4,5,6,7,8,9};

    int (&int_ref)[10] = k; //這樣就得到了一個數組的引用

        printValues(&i); // error: argument is not an array of 10 ints

        printValues(j); // error: argument is not an array of 10 ints

        printValues(k); // ok: argument is an array of 10 ints

        return 0;

    }

    @arr的圓括號是不能少的,因為下標操作符具有更高的優先級。

    f(int &arr[10])     // error: arr is an array of references

    f(int (&arr)[10]) // ok: arr is a reference to an array of 10 ints

    再多說點,關于類型定義

    想要定義一個數組引用類型,方法如下

    typedef 類型名 (&數組引用類型名)[N];

    實例

    typedef int (&Array_Ref)[10];

    Array_Ref就是一個數組的引用類型了。

    16.1.6. 編寫泛型程序 Writing Generic Programs

    當模板定義完成后,模板實際上認為類型都是有效的。但是實際使用模板的用戶,所使用的類型有可能就是無效的。

    生成的程序是否合法取決于函數中用到的操作以及類型能夠支持的操作。(Whether the generated program is legal depends on the operations used in the function and the operations supported by the type or types used.)說出來很拗口,但是稍微看一下大師給的例子,就明鏡一樣的啦。

    if (v1 < v2) return -1; // < on two objects of type T

    if (v2 < v1) return 1; // < on two objects of type T

    return 0;               // return int; not dependent on T

    如果對象類型不支持’<’翹了L就比如說Sale_item

    編寫和類型無關的代碼

    原則:

    編寫模板代碼時,對類型的需求盡可能少。(When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.

    開始的例子compare就能夠說明上面的原則。

    模板的形參是const引用,就避免了copy,這樣不允許copy的類類型也可以使用這個模板了。

    模板函數體只使用<操作符。減少了支持的操作符

    // expected comparison

    if (v1 < v2) return -1;

    if (v1 > v2) return 1;

    return 0;

    // expected comparison

    if (v1 < v2) return -1;

    if (v2 < v1) return 1; // equivalent to v1 > v2

    return 0;

    后者對類類型的需求要少些,不必支持’<’’>’兩種操作符。

    模板鏈接時的編譯錯誤

    當編譯模板時,有三個階段可以標識錯誤:

    1.    編譯模板本身時

    2.    當編譯器看到一個模板使用時

    3.    在模板實例化時

    16.2 Instantiation

    模板實例化定義

    模板是一個藍本,模板本身不是類或者函數。編譯器使用模板生成特定類型版本的類或者函數。這個特定類型的模板實例過程叫做實例化。(A template is a blueprint; it is not itself a class or a function. The compiler uses the template to generate type-specific versions of the specified class or function. The process of generatng a type-specific instance of a template is known as instantiation.

    類實例化:

    這是模板的定義:

    template <class Type> class Queue {

    public:

        Queue ();                // default constructor

        Type &front ();          // return element from head of Queue

        const Type &front () const;

        void push (const Type &); // add element to back of Queue

        void pop();              // remove element from head of Queue

        bool empty() const;      // true if no elements in the Queue

    private:

        // ...

    };

    Queue<int> qi;這句話對應的就是實例化類Queue<int>

    編譯器通過重寫模板Queue生成Queue<int>類。就是說每一個模板類實例化,實際上都會產生新的類,并且由模板類生成的類之間是沒有任何關系的。Queue<int>    Queue<string> 是沒有任何關系的,雖然它們都是由同一個類模板Queue生成的。

    每個類模板實例化生成一個獨立的類類型。(Each instantiation of a class template constitutes an independent class type.

    // simulated version of Queue instantiated for type int

    template <class Type> class Queue {

    public:

        Queue();                  // this bound to Queue<int>*

        int &front();             // return type bound to int

        const int &front() const; // return type bound to int

        void push(const int &);   // parameter type bound to int

        void pop();               // type invariant code

        bool empty() const;       // type invariant code

    private:

        // ...

    };

    函數模板實例化

    函數模板實例化會生成不同類型版本的函數。

    16.2.1模板實參推斷 Template Argument Deduction

    從函數實參的類型確定模板實參的類型和值的過程叫做模板實參推斷。(The process of determining the types and values of the template arguments from the type of the function arguments is called template argument deduction.

    多種類型形參的實參必須完全匹配

    初聽起來,像天書,其實說的是

    模板類型參數有可能用作是函數的一個或者多個形參。這種情況下,模板類型推斷必須對每一對應的函數實參生成同樣的模板實參類型。(A template type parameter may be used as the type of more than one function parameter. In such cases, template type deduction must generate the same template argument type for each corresponding function argument.

    還是拗口,看例子就一目了然。

    //模板定義:

    template <typename T>

    int compare(const T& v1, const T& v2)

    {

        if (v1 < v2) return -1;

        if (v2 < v1) return 1;

        return 0;

    }

    int main()

    {

        short si;

        //實例化,翹了L

        // error: cannot instantiate compare(short, int)

        // must be: compare(short, short) or

        // compare(int, int)

        compare(si, 1024);

        return 0;

    }

    類型形參的實參的有限轉換

    編譯器只能支持兩種類型的轉換,而不需實例化。

    ·           const轉換。

    如果函數的形參是指向const對象的引用或指針,這個函數可以被非const對象的引用或指針調用。(A function that takes a reference or pointer to a const can be called with a reference or pointer to nonconst object,

    如果函數接受非引用類型,那么在形參類型或實參上都會忽略const。(If the function takes a nonreference type, then const is ignored on either the parameter type or the argument.

    這就是說我們是否傳遞一個const或非const對象給一個接受非引用類型的函數,都會使用同一個實例。(That is, the same instantiation will be used whether we pass a const or nonconst object to a function defined to take a nonreference type.

    ·           數組或函數到指針的轉換。

    如果模板形參不是引用類型,那么數組或函數類型的實參可以使用正常的指針轉換。(If the template parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type.

    數組實參可以看作是指向第一個元素的指針,函數實參可以看作是指向函數類型的指針。(An array argument will be treated as a pointer to its first element, and a function argument will be treated as a pointer to the function's type.

    正常的轉換依然可以用在非模板類型的實參上。

    這句話的意思是,上面所討論的轉換模式僅限于模板類型形參。

    模板實參和函數指針

    復習函數指針:

    //pf是一個函數指針,函數的返回值是bool,形參是兩個string類型的引用

    bool (*pf)(const string &, const string &);

    //函數定義

    bool lengthCompare(const string &, const string &);

    //函數指針賦值

    pf= lengthCompare

    通過模板實參定義函數指針:

    template <typename T> int compare(const T&, const T&);

    // pf1 points to the instantiation int compare (const int&, const int&)

    int (*pf1) (const int&, const int&) = compare;

    一旦函數模板實例的地址可以得到,上下文必須為每個模板形參確定唯一的類型或者值。(When the address of a function-template instantiation is taken, the context must be such that it allows a unique type or value to be determined for each template parameter.

    16.2.2.函數模板的顯式實參 Function-Template Explicit Arguments

    為什么要使用顯式實參?

    這是因為在某些特殊情況下,是不可能推斷出模板實參的類型的。問題都是出在函數的返回值類型和形參列表中的類型不同。(This problem arises most often when a function return type must be a type that differs from any used in the parameter list.)這種情況下,就需要顯式指定模板形參的類型或者值。

    指定顯式模板實參

    這種方法就是由模板用戶來決定返回值類型。

    // T or U as the returntype?模板定義

    template <class T, class U> ??? sum(T, U);

    // ok: now either T or U works as return type

    int i; short s;

    sum(static_cast<int>(s), i); // ok: instantiates int sum(int, int)

    在使用模板前預判斷。這個判斷要由模板用戶來完成。

    返回值類型使用類型形參

    就是說為函數的返回值也指定類型形參。

    模板定義:

    // T1 cannot be deduced: it doesn't appear in the function parameter list

    //在模板實參推斷時,T1是不起作用的。

    template <class T1, class T2, class T3>

    T1 sum(T2, T3);

    模板實例化:

    // ok T1 explicitly specified; T2 and T3 inferred from argument types

    long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)

    顯式模板實參是按從左到由的順序進行匹配的。

    顯式實參和函數模板指針

    template <typename T> int compare(const T&, const T&);

    // overloaded versions of func; each take a different function pointer type

    void func(int(*) (const string&, const string&));

    void func(int(*) (const int&, const int&));

    func(compare<int>); // ok: explicitly specify which version of compare

    應用場景:

    funcoverload的,它的形參可以是不同的函數指針。因此可以使用顯式實參來指定函數指針,也就是說指定模板的實例。

    16.3  模板編譯模式Template Compilation Models

    只有當編譯器發現模板使用的時候,才生成代碼。

    一般程序的編譯過程

    先說說編譯器對一般的函數調用的編譯方法。單獨調用一個函數時,編譯器只需要看到這個函數的聲明;類似,當定義一個類類型的對象時,類定義必須是有效的,但是成員函數的定義不必出現。這樣我們就可以把函數聲明以及類定義放在頭文件里,而函數定義以及類成員函數的定義都放在源文件里。(Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we define an object of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.

    包含編譯模型Inclusion Compilation Model

    頭文件utlities.h

    #ifndef UTLITIES_H // header gaurd (Section 2.9.2, p. 69)

    #define UTLITIES_H

    template <class T> int compare(const T&, const T&);

    // other declarations

    #include "utilities.cc" // get the definitions for compare etc.

    #endif

    utilities.cc

    template <class T> int compare(const T &v1, const T &v2)

    {

        if (v1 < v2) return -1;

        if (v2 < v1) return 1;

        return 0;

    }

    分別編譯模型Separate Compilation Model

    模板函數:

    在模板函數定義的源文件中把模板定義為export

    // the template definition goes in a separately-compiled source file

    export template <typename Type>

    Type sum(Type t1, Type t2) /* ..模板定義.*/

    但是在頭文件的聲明中就無需再定義為export

    模板類:

    類定義在頭文件里:

    // class template header goes in shared header file

    template <class Type> class Queue { ... }; /* ..模板類定義.*/

    源文件Queue.cc定義成員函數:

    // 在源文件中把類聲明為exported

    export template <class Type> class Queue;

    #include "Queue.h"

    // Queue member definitions

    Export關鍵字表示給出的定義可能需要在其它的文件里生成實例。(The export keyword indicates that a given definition might be needed to generate instantiations in other files.

    16.4 Class Template Members

    這部分主要是講述如何定義類模板的成員。

    16.4.1 類模板成員函數Class-Template Member Functions

    類模板成員函數定義的固定格式:

    1. 關鍵字template開頭,后面跟著模板形參。
    2. 說明類成員
    3. 類名字必須包括模板形參

    對于Queue,它的成員函數在類外定義,就必須是一下的格式:

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

    具體到destroy函數:

    template <class Type> void Queue<Type>::destroy()

    {

        while (!empty())

            pop();

    }

    類模板成員函數的實例化

    首先類模板的成員函數它們自己本身也是函數模板。

    但是和函數模板不同的地方在模板實參推斷上。細細說明:

    1. 函數模板的實參推斷是在函數調用時發生的。實參推斷(template-argument deduction),然后就是函數模板實例化。
    2. 類模板成員函數呢。當類模板成員函數實例化時編譯器不執行實參推斷。類模板成員函數的模板形參取決于調用這個函數的對象的類型。(Instead, the template parameters of a class template member function are determined by the type of the object on which the call is made.)舉例說,就是,當我們調用Queue<int>類型對象的push成員函數時,push函數實例化為:

    void Queue<int>::push(const int &val)

    這樣,因為不存在實參推斷(template-argument deduction),如果函數形參是采用模板形參來定義,允許函數實參執行普通類型轉換:Normal conversions are allowed on arguments to function parameters that were defined using the template parameter:

    Queue<int> qi; // instantiates class Queue<int>

    short s = 42;

    int i = 42;

    // ok: s converted to int and passed to push

    qi.push(s); // instantiates Queue<int>::push(const int&)

    qi.push(i); // uses Queue<int>::push(const int&)

    f(s);       // instantiates f(const short&)

    f(i);       // instantiates f(const int&)

    類和成員啥時實例化

    只有程序用到的類模板的成員函數才實例化。

    這是P309的例子,這個序列容器初始化函數只有一個size形參。調用容器的這個構造函數來初始化一個對象,實際上要用到單元類型的缺省構造函數。

    const list<int>::size_type list_size=64;

    list<int> ilist(list_size);

    當我們定義一個模板類型的對象時,類模板就會實例化。同時還會實例化任何用來初始化對象的構造函數,以及構造函數調用的任何成員。(When we define an object of a template type, that definition causes the class template to be instantiated. Defining an object also instantiates whichever constructor was used to initialize the object, along with any members called by that constructor:

    // instantiates Queue<int> class and Queue<int>::Queue()

    Queue<string> qs;

    qs.push("hello"); // instantiates Queue<int>::push

    16.4.2. 非類型形參的模板實參Template Arguments for Nontype Parameters

    非類型模板實參必須是編譯期的常量表達式。(Nontype template arguments must be compile-time constant expressions.

    16.4.3. 類模板中的友元聲明Friend Declarations in Class Templates

    大體可以分成三類:

    普通友元:

    template <class Type> class Bar {

        // grants access to ordinary, nontemplate class and function

        friend class FooBar;

        friend void fcn();

        // ...

    };

    一般模板友元關系:(General Template Friendship

    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&);

        // ...

    };

    建立的是一種一對多的映射關系。對于每一個Bar實例,所有的Fooltemp_fcn1實例都是它的友元。

    特定模板友元

    區別于一般模板友元,類可以只允許特定的實例訪問。下面的這個例子,只允許char*Foo2templ_fcn2作為Bar的友元。

    template <class T> class Foo2;

    template <class T> void templ_fcn2(const T&);

    template <class Type> class Bar {

         // grants access to a single specific instance parameterized by char*

         friend class Foo2<char*>;

         friend void templ_fcn2<char*>(char* const &);

         // ...

    };

    更加通用的寫法是只允許和Bar類型一致的類或者函數模板實例作為友元:

    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&);

        // ...

    };

    聲明依賴性(Declaration Dependencies

    當模板的所有實例都可以訪問時,也就是說模板是友元時,在作用域里不必聲明這個類模板或者函數模板。編譯器把友元聲明當作是類或函數聲明。(When we grant access to all instances of a given template, there need not be a declaration for that class or function template in scope. Essentially, the compiler treats the friend declaration as a declaration of the class or function as well.

    例如:template <class S> friend class D;

    如果限制只有特定的實例才能作為友元,那么類模板或者函數模板就必須先聲明,才能用作友元聲明。(When we want to restrict friendship to a specific instantiation, then the class or function must have been declared before it can be used in a friend declaration:

    例如:friend class A<T>; 這是正確的聲明

    friend class E<T>;錯誤的聲明,因為類模板E沒有在前面聲明。

    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

     };

    16.4.4. Queue 和 QueueItem 的友元聲明 Queue and QueueItem Friend Declarations

    QueueItem的友元應該是和它同類型的Queue

    template <class Type> class Queue;

    template <class Type> class QueueItem {

        friend class Queue<Type>;

        // ...

     };

    輸出操作符重載’<<’

    重新溫習一下14.2輸入和輸出操作符,關于為什么輸出操作符必須是非成員函數。

    重載輸出操作符一般的簡單定義如下:

    // general skeleton of the overloaded output operator

    ostream&

    operator <<(ostream& os, const ClassType &object)

    {

        // any special logic to prepare object

        // actual output of members

        os << // ...

        // return ostream object

        return os;

    }

    然后再拉回到模板類Queue

    template <class Type>

    ostream& operator<<(ostream &os, const Queue<Type> &q)

    {

        os << "< ";

        QueueItem<Type> *p;

        for (p = q.head; p; p = p->next)

                os << p->item << " ";

        os <<">";

        return os;

    }

    輸出操作符重載,需要直接訪問QueueheadQueueItemnext方法和item數據成員,因此輸出操作符重載就必須是QueueQueueItem的友元。

    // 函數模板聲明必須先于友元聲明

    template <class T> std::ostream& operator<<(std::ostream&, const Queue<T>&);

    template <class Type> class QueueItem {

        friend class Queue<Type>;

        // needs access to item and next

        friend std::ostream&

        operator<< <Type> (std::ostream&, const Queue<Type>&);

        // ...

    };

    template <class Type> class Queue {

        // needs access to head

       friend std::ostream&

        operator<< <Type> (std::ostream&, const Queue<Type>&);

    };

    16.4.5. 成員模板 Member Templates

    成員模板

    類的成員是類模板或者是函數模板,這樣的成員就是成員模板。成員模板不能是虛函數。(Any class (template or otherwise) may have a member that is itself a class or function template. Such members are referred to as member templates. Member templates may not be virtual.

    定義

    這部分是以Queue中拷貝構造函數(copy constructor)和賦值操作(assignment operator)來舉例說明的。這兩個函數都是模板函數。

    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);

    };

    在類定義以外定義成員模板

    當成員模板是類模板的成員時,成員模板的定義必須包含類模板的形參以及成員函數自身的模板形參。類模板的形參在前,緊跟著是成員函數自己的模板形參列表。(When a member template is a member of a class template, then its definition must include the class-template parameters as well as its own template parameters. The class-template parameter list comes first, followed by the member's own template parameter list.

    // template <class Type>類模板形參

    // template <class It>成員模板形參

    template <class Type> template <class It>

    void Queue<Type>::copy_elems(It beg, It end)

    {

        while (beg != end) {

           push(*beg);

           ++beg;

        }

    }

    成員模板和實例化

    只有當成員模板被使用時,它才被實例化。

    成員模板包含有兩類模板形參:

    1.    由類定義的模板形參。這部分是固定的,由調用函數的對象決定。(The class template parameters are fixed by the type of the object through which the function is called.

    2.    由成員模板自己定義的模板形參。由模板實參推斷確定。(These parameters are resolved through normal template argument deduction

    16.4.7. 類模板的 static 成員 static Members of Class Templates

    類模板靜態成員聲明

    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

    };

    類模板是可以定義靜態成員的。但是和一般的類不同,每一個類模板的實例都有自己的靜態成員。

    使用類模板的靜態成員

    類模板的靜態成員可以通過類類型的對象來訪問,或者使用作用域操作符直接訪問。當然,通過類來使用靜態成員,必須引用實際的實例。(we can access a static member of a class template through an object of the class type or by using the scope operator to access the member directly. Of course, when we attempt to use the static member through the class, we must refer to an actual instantiation:

    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?

    定義靜態成員

    以關鍵字template開頭,緊跟著類模板形參列表和類名。(It begins with the keyword template followed by the class template parameter list and the class name.

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

    16.5泛型句柄類A Generic Handle Class

    其實大師前面講過兩種Handler類:

    1.      Sales_item:指針型句柄(Pointerlike Handle)(參看:15.8.1. 指針型句柄)

    class Sales_item {

    public:

        // default constructor: unbound handle

        Sales_item(): p(0), use(new std::size_t(1)) { }

        // attaches a handle to a copy of the Item_base object

        Sales_item(const Item_base&);

        // copy control members to manage the use count and pointers

        Sales_item(const Sales_item &i):

                          p(i.p), use(i.use) { ++*use; }

        ~Sales_item() { decr_use(); }

        Sales_item& operator=(const Sales_item&);

        // member access operators

        const Item_base *operator->() const { if (p) return p;

            else throw std::logic_error("unbound Sales_item"); }

        const Item_base &operator*() const { if (p) return *p;

            else throw std::logic_error("unbound Sales_item"); }

    private:

        Item_base *p;        // pointer to shared item

        std::size_t *use;    // pointer to shared use count

        // called by both destructor and assignment operator to free pointers

        void decr_use()

             { if (--*use == 0) { delete p; delete use; } }

    };

    2.      Query:值型句柄(Valuelike Handle

    // handle class to manage the Query_base inheritance hierarchy

    class Query {

        // these operators need access to the Query_base* constructor

        friend Query operator~(const Query &);

        friend Query operator|(const Query&, const Query&);

        friend Query operator&(const Query&, const Query&);

    public:

        Query(const std::string&); // builds a new WordQuery

        // copy control to manage pointers and use counting

        Query(const Query &c): q(c.q), use(c.use) { ++*use; }

        ~Query() { decr_use(); }

        Query& operator=(const Query&);

        // interface functions: will call corresponding Query_base operations

        std::set<TextQuery::line_no>

          eval(const TextQuery &t) const { return q->eval(t); }

        std::ostream &display(std::ostream &os) const

                                { return q->display(os); }

    private:

        Query(Query_base *query): q(query),

                                  use(new std::size_t(1)) { }

        Query_base *q;

        std::size_t *use;

        void decr_use()

        { if (--*use == 0) { delete q; delete use; } }

    };

    二者相比較:

    相同點:都保存一個類型對象的指針;并且都包含一個使用計數的指針。

    不同點:

    ·       值型句柄只為保存的對象提供接口。值型句柄不定義解引用和箭頭操作符的。值型句柄包含的的對象的類類型是沒有public成員的。

    ·       值型句柄包含的的對象的類類型是沒有public成員的。對這個類的訪問都是通過值型句柄來完成的。這可以理解為值型句柄為保存的對象提供接口。

    問題是難道我們要寫N多的各種各樣的Handler類嗎?No,No,No,泛型編程。我們可以定義一個類模板來管理指針并完成使用計數的功能。(This kind of problem is well suited to generic programming: We could define a class template to manage a pointer and do the use-counting.

    16.5.1. 定義句柄類Defining the Handle Class

    賦值操作符的定義可以參看16.4.5 成員模板(Member Templates

    16.5.2. 使用句柄(Using the Handle)

    有必要重新復習一下箭頭操作符重載P525

    point->action();

    由于優先級規則,它實際等價于編寫:

    (point->action)();

    編譯器執行這段代碼:

    1.      如果point是對象,并且這個對象所屬的類定義了operator->。那么就等價于:

    point.operator->()->action

    L之所以繞不出來,就在這里,一定以要記住這個等價表示,它包含了兩個’->’

    2.      如果point是指向對象的指針,那么編譯器調用對象的action方法

    使用句柄

    調用 net_price 函數的語句值得仔細分析一下:

    sum += (*iter)->net_price(items.count(*iter));

    唉,你不得不服啊,大師,選的這的這語句。

    ·           (*iter) 返回Sales_item對象。(書中說返回的是h,但是我覺得是Sales_item對象)

    ·           因此,(*iter)-> 使用句柄類Sales_item的重載箭頭操作符。

    等價于:

    (*iter). operator->()->net_price(items.count(*iter));

    ·           編譯器計算 h.operator->(),獲得該 Handle 對象保存的 Item_base 指針。

    16.6 模板特化(Template Specializations

    lzn曾經和我說起,這是C++的一個重要特性。也是因為它,使得C++JavaC#更加的靈活。今天終于看到這部分了,excitedJ

    模板特化的背景是有時模板類或者模板函數并不適合所有的類型(type)。但是對于模板用戶來說這種特化又是透明的。

    16.6.1. 函數模板的特化 Specializing a Function Template

    對比在一起看或許更明了。前面的是模板函數compare

    template <typename T>

    int compare(const T &v1, const T &v2)

    {

       if (v1 < v2) return -1;

       if (v2 < v1) return 1;

       return 0;

    }

    這是特化函數模板:

    關鍵字template后面跟的是空的尖括號對<>

    然后跟著模板名稱和尖括號對指定的模板形參。

    函數形參表

    函數體

    template <>

    int compare<const char*>(const char* const &v1, const char* const &v2)

    {

        return strcmp(v1, v2);

    }

    聲明模板特化

    特化也可以簡單的聲明,而不被定義,其實聲明和定義很類似,只是缺少了函數體。

    template<> int compare<const char*>(const char* const&, const char* const&);

    template<> int compare(const char* const&, const char* const&);

    這兩個聲明都是有效的,后者之所以有效,是因為模板的實參可以通過函數的形參列表推斷出來,因此就不需要顯式定義模板的實參了。(If the template arguments can be inferred from the function parameter list, there is no need to explicitly specify the template arguments

    函數重載和模板特化

    這樣寫是模板特化:

    template<> int compare<const char*>(const char* const&, const char* const&);

    但是如果這樣寫就是普通的函數重載:

    int compare(const char* const&, const char* const&);

    模板特化和函數是不一樣的。前者不存在類型轉換(conversions)。如果是調用普通的函數,對實參可以應用類型轉換;如果特化模板,實參類型是不能應用類型轉換的。實參類型必須完全匹配特化版本函數形參類型。

    重復定義不總是被檢測到

    應在一個頭文件中包含模板特化的聲明,然后使用該特化的每個源文件包含該頭文件。(declarations for template specializations should be included in a header file. That header should then be included in every source file that uses the specialization.

    普通的作用域規則也可以應用到特化上

    簡單講,就是要先聲明后使用。這句話有兩層含義:

    1.      在模板特化定義或者聲明之前要先聲明相應的模板。

    2.      同樣在特化模板調用前,要先對特化模板進行聲明。

    16.6.2. 類模板的特化Specializing a Class Template

    定義類特化Defining a Class Specialization

    還是放在一起看:

    template <class Type> class Queue {

    public:

       void push(const Type &);  

    …}

    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

    };

    類特化實際上只是和原來的模板類具有相同的接口,而實現上可以完全不同。例如Queue<const char*>,它的數據成員只有一個Queue<std::string> real_queue;

    類模板特化應該定義和它特化的模板相同的接口。如果不這樣,當模板用戶使用未定義的成員時,就會感到很吃驚。(A class template specialization ought to define the same interface as the template it specializes. Doing otherwise will surprise users when they attempt to use a member that is not defined.

    類特化定義Class Specialization Definition

    在類特化之外定義一個成員時,前面可以沒有前綴template<>

    void Queue<const char*>::push(const char* val)

     {

         return real_queue.push(val);

     }

    16.6.3. 特化成員而不特化類Specializing Members but Not the Class

    應用場景:只特化某些成員。

    例如對于Queue,其實只需特化push()pop()這兩個成員函數(member)。這樣可以才應特化成員,而不是前面特化類的方法。

    特化聲明:它必須以template <>開頭。

    // push and pop specialized for const char*

    template <> void Queue<const char*>::push(const char* const &);

    template <> void Queue<const char*>::pop();

    16.6.4. 類模板的部分特化Class-Template Partial Specializations

    應用場景:類模板有多個模板形參,我們也許想要部分模板形參。

    類模板特化本身也是模板。(A class template partial specialization is itself a template.

    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> {

         // ...

     };

    使用:

    some_template<int, string> foo; // uses template

    some_template<string, int> bar; // 使用部分特化

    16.7 重載和函數模板(Overloading and Function Templates)

    函數模板也能夠重載。正是因為這個原因,才引出這部分內容。

    函數匹配與函數模板(Function Matching and Function Templates

    確定函數調用的步驟:

    1. 構件候選函數集合。候選函數包括:

    (a)同名的一般函數。(Any ordinary function with the same name as the called function.

    (b)函數模板實例。模板實參檢測發現模板實參和函數實參匹配。(Any function-template instantiation for which template argument deduction finds template arguments that match the function arguments used in the call.

    1. 決定哪些一般函數是有效的。(Determine which, if any, of the ordinary functions are viable
    2. 通過轉換的類型(如果需要轉換才能調用的話),對于有效的函數進行歸類。(Rank the viable functions by the kinds of conversions, if any, required to make the call

    (a) 如果只剩一個函數,調用這個函數。

    (b)如果調用有二義性,從有效函數集合中去掉函數模板實例。(If the call is ambiguous, remove any function template instances from the set of viable functions.

    1. 重新歸類,排除函數模板實例。(Rerank the viable functions excluding the function template instantiations.

    ·       如果只剩一個函數,調用這個函數。(If only one function is selected, call this function.

    ·       否則,調用具有二義性。(Otherwise, the call is ambiguous.

    舉例說明函數模板匹配

    函數和函數模板定義:

    // compares two objects

    template <typename T> int compare(const T&, const T&);

    // compares elements in two sequences

    template <class U, class V> int compare(U, U, V);

    // plain functions to handle C-style character strings

    int compare(const char*, const char*);

    調用:

    普通函數和第一個函數模板都是匹配的,但是根據規則3(b),普通函數優先。

    const char const_arr1[] = "world", const_arr2[] = "hi";

    // calls the ordinary function taking const char* parameters

    compare(const_arr1, const_arr2);

    普通函數的形參是const指針(const char*),第一個模板函數的形參是const引用。這兩個函數調用都要做類型轉換:從數組轉換到指針。普通函數優先。

    // calls the ordinary function taking const char* parameters

    char ch_arr1[] = "world", ch_arr2[] = "hi";

    compare(ch_arr1, ch_arr2);

    轉換和重載函數模板

    調用:

    char *p1 = ch_arr1, *p2 = ch_arr2;

    compare(p1, p2);

    函數模板template <typename T> int compare(const T&, const T&);

    char *綁定到T上,這樣函數的形參是const的,指向char的指針的,引用。無需類型轉換。(7.2.2. 引用形參)

    函數調用int compare(const char*, const char*);

    需要把char * 轉換為const char*。要類型轉換

    結論:定義函數模板特化好于使用非模板函數。(it is almost always better to define a function-template specialization than to use a nontemplate version.

    posted on 2009-07-28 17:29 amenglai 閱讀(4525) 評論(1)  編輯  收藏 所屬分類: C++ Primer 之 讀書筆記

    評論

    # re: C++ Primer 之 讀書筆記 第十六章 模板和泛型編程  回復  更多評論   

    template <class Type> class Queue
    template <class Type> class Queue<int>
    書上這兩個函數簽名有什么不同嗎?你寫的里面好像只有第一個
    2010-09-25 16:23 | guix
    主站蜘蛛池模板: 亚洲国产AV无码专区亚洲AV| 亚洲精品无码日韩国产不卡av| 2019中文字幕免费电影在线播放| 亚洲欧洲日产专区| 国产成人综合久久精品免费 | 亚洲a级片在线观看| 日本特黄特色免费大片| 中国一级毛片免费看视频| 亚洲一区二区三区91| 亚洲乱码中文字幕手机在线| 午夜理伦剧场免费| 朝桐光亚洲专区在线中文字幕 | 免费女人18毛片a级毛片视频| 国产午夜无码片免费| va天堂va亚洲va影视中文字幕| 亚洲国产精品一区二区九九| 国产人成免费视频网站| sihu国产精品永久免费| 亚洲一线产区二线产区精华| 国产亚洲精品线观看动态图| 国产亚洲精品看片在线观看| xxxxwww免费| 国产免费一级高清淫曰本片| 国产午夜亚洲精品国产| 久久综合图区亚洲综合图区| 免费看男女下面日出水视频| 人与禽交免费网站视频| 好男人资源在线WWW免费| 亚洲国产精品美女久久久久| 夜夜亚洲天天久久| 亚洲精品在线视频| 午夜精品在线免费观看| 久久不见久久见免费视频7| 成人免费夜片在线观看| 久久亚洲国产最新网站| 久久久久久a亚洲欧洲AV| 亚洲色图综合在线| 又黄又爽的视频免费看| 最近免费中文字幕大全视频 | 亚洲国产精品久久66| 亚洲视频在线免费|