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

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

16.4 类模板成员

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

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

Queue<Type>(const Queue<Type> &Q): head(0), tail(0)

{ copy_elems(Q); }

编译器不会为类中使用的其他模板的模板形参进行这样的推断,因此,在声明伙伴类 QueueItem 的指针时,必须指定类型形参:

QueueItem<Type> *head; // pointer to first element in Queue

2. 类模板成员函数 : 类模板成员函数的定义具有如下形式:

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

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

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

从这些规则可以看到,在类外定义的 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<int> class and Queue<int>::Queue()

Queue<string> qs;

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

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

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

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

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

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

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

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

// ...

};

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

这个友元声明在 Bar 与其友元 Foo1 和 temp1_fcn1 的每个实例之间建立了一对多的映射。对 Bar 的每个实例而言,Foo1 或 temp1_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 的特定实例与使用同一模板实参的 Foo3 或 temp1_fcn3 的实例之间的友元关系。每个 Bar 实例有一个相关的 Foo3 和 temp1_fcn3 友元:

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

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

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

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

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

};

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

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

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 成员各有一个模板类型形参,这些函数使用该类型形参作为其函数形参的类型,它们的函数形参是指明要复制元素范围的迭代器。

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

template <class T> template <class Iter> void Queue<T>::assign(Iter beg,Iter end){

destroy();

copy_elems(beg,end);

}

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

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

12.成员模板和实例化:与其他成员一样,成员模板只有在程序中使用时才实例化。

类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样。

13.类模板可以像任意其他类一样声明 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 成员 ctr,Foo<string> 类型的对象共享另一个不同的 ctr 成员。

14.使用类模板的 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 成员函数只有在程序中使用时才进行实例化。

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

template <class T>

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

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

16.6 模板特化

1.函数模板的特化:模板特化(template specialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:

a.关键字 template 后面接一对空的尖括号(<>);

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

c.函数形参表;

d.函数体。

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

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

5.为 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

};

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

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

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

{

return real_queue.push(val);

}

7.特化成员而不特化类:成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参表开头:

// push and pop specialized for const char*

template <>

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

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

8.类模板的部分特化:

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

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。部分特化的模板形参表只列出未知模板实参的那些形参。

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

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

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

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

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

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

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

16.7 重载与函数模板

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

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

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

a. 为这个函数名建立候选函数集合,包括:

与被调用函数名字相同的任意普通函数。

任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参。

b. 确定哪些普通函数是可行的(如果有可行函数的话)。候选集合中的每个模板实例都 可行的,因为模板实参推断保证函数可以被调用。

c. 如果需要转换来进行调用,根据转换的种类排列可靠函数,记住,调用模板函数实例所允许的转换是有限的。

如果只有一个函数可选,就调用这个函数。

如果调用有二义性,从可行函数集合中去掉所有函数模板实例。

d. 重新排列去掉函数模板实例的可行函数。

如果只有一个函数可选,就调用这个函数。

否则,调用有二义性。

时间: 2024-10-08 08:15:57

C++学习笔记十六-模板和泛型编程(二)的相关文章

Java基础学习笔记十六 集合框架(二)

List List接口的特点: 它是一个元素存取有序的集合.例如,存元素的顺序是11.22.33.那么集合中,元素的存储就是按照11.22.33的顺序完成的. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理). 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素. List接口的常用子类有: ArrayList集合 LinkedList集合 List接口的特有方法(带索引的方法)1.增加元素方法 add(Object e):向集合末尾

MYSQL进阶学习笔记十六:MySQL 监控!(视频序号:进阶_35)

知识点十六:MySQL监控(35) 一.为什么使用MySQL监控 随着软件后期的不断升级,myssql的服务器数量越来越多,软硬件故障的发生概率也越来越高.这个时候就需要一套监控系统,当主机发生异常时,此时通过监控系统发现和处理. 这个监控实际上是在我们的开发完成之后,这个时候软件就开始在运行,这个运行我们就需要去关注到mysql服务器是否正常,那么我们要观察它就需要给它提供一些监控,这监控就是当它发生故障之后, 那么我们这个监控就会告诉我们到底什么地方发生了一些异常或者一些错误,这个时候我们就

python学习笔记十六 django深入学习一

django 请求流程图 django 路由系统 在django中我们可以通过定义urls,让不同的url路由到不同的处理函数 from . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), #精确匹配 url(r'^articles/([0-9]{4})/$', views.year_archive), #动态路由 url(r'^articles/([0-9]{4})/([0-9]{2

spring in action学习笔记十六:配置数据源的几种方式

第一种方式:JNDI的方式. 用xml配置的方式的代码如下: 1 <jee:jndi-lookup jndi-name="/jdbc/spittrDS" resource-ref="true" id="dataSource"/> 用注解方式的代码如下: 1 @Bean 2 public JndiObjectFactoryBean jndiObjectFactoryBean(){ 3 JndiObjectFactoryBean jndi

yii2源码学习笔记(十六)

Module类的最后代码 1 /** 2 * Registers sub-modules in the current module. 3 * 注册子模块到当前模块 4 * Each sub-module should be specified as a name-value pair, where 5 * name refers to the ID of the module and value the module or a configuration 6 * array that can

[傅里叶变换及其应用学习笔记] 十六. 继续上次内容,晶体成像

x射线晶体照像术 1) x射线是1895年由伦琴(Roentgen)发现的,其波长为$10^{-8}$厘米左右,常用的测量可见光波长的方法会由于其波长太小而无法测量. 2) 晶体(Crystals),晶体的原子结构符合一定规律——原子有序地排列成晶格.劳厄(Laue)在1912年做了一系列著名实验,其目的是利用x射线进行衍射实验来研究晶体的本质. 劳尔假设: 1) x射线是波,因此可以进行衍射 2) 晶体可以充当合适的衍射光栅,即晶体具有晶格原子(lattice atomic)——周期性的原子结

JavaScript权威设计--CSS(简要学习笔记十六)

1.Document的一些特殊属性 document.lastModified document.URL document.title document.referrer document.domain document.write() document.writeIn() 2.查询选取的文本 使用鼠标mouseup事件 3.浏览器定义了多项文本编辑命令(富文本编辑器) 使用Document对象的execCommand()方法. document.queryCommandSupport()判断浏

Android学习笔记十六.Android数据存储与IO.SharedPreferences

SharedPreferences 对于应用程序的数据输入.输出,如果是应用程序只是少量数据需要保存,那么使用普通文件就可以了(SharedPrefereces);但如果应用程序有大量数据需要存储.访问,就需要借助数据库了.Android系统内置了SQLite数据库,SQLite数据库是一个真正轻量级的数据库,它没有后台进程,整个数据库就对应于一个文件. 1.SharedPreferences简介 (1)概念:SharedPreferences保存的数据主要是类似于配置信息格式的数据,因此它保存

PHP学习笔记十六【方法】

<?php //给一个函数传递基本数据类型 $a=90; $b=90.8; $c=true; $d="hello world"; function test1($a,$b,$c,$d) //test1(&$a,&$b,&$c,&$d)//如果是传地址那么值就会改变 { $a=78; $b=89.5; $c=false; $d="beijing"; } //调用函数 test1($a,$b,$c,$d); echo $a.&quo