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 阅读(4454) 评论(1)  编辑  收藏 所属分类: C++ Primer 之 读书笔记

评论

# re: C++ Primer 之 读书笔记 第十六章 模板和泛型编程  回复  更多评论   

template <class Type> class Queue
template <class Type> class Queue<int>
书上这两个函数签名有什么不同吗?你写的里面好像只有第一个
2010-09-25 16:23 | guix

只有注册用户登录后才能发表评论。


网站导航: