博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++ 模板与泛型编程
阅读量:5806 次
发布时间:2019-06-18

本文共 12185 字,大约阅读时间需要 40 分钟。

《C++ Primer 4th》读书笔记

所谓泛型编程就是以独立于任何特定类型的方式编写代码。泛型编程与面向对象编程一样,都依赖于某种形式的多态性。

面向对象编程中的多态性在运行时应用于存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。

在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。

面向对象编程所依赖的多态性称为运行时多态性,泛型编程所依赖的多态性称为编译时多态性或参数式多态性。

 

模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。

 

函数模板

模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。

template 
int compare(const T &v1, const T &v2){if (v1 < v2) return -1;if (v2 < v1) return 1;return 0;}

 

模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。类型形参跟在关键字 class 或 typename 之后定义.在函数模板形参表中,关键字 typename 和 class 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:

// ok: no distinction between typename and class in template parameter listtemplate 
calc (const T&, const U&);

 

模板形参表示可以在类或函数的定义中使用的类型或值。使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。

实质上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。推导出实际模板实参后,编译器使用实参代替相应的模板形参产生编译该版本的函数。编译器承担了为我们使用的每种类型而编写函数的单调工作。

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

 

 函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。

// ok: inline specifier follows template parameter listtemplate 
inline T min(const T&, const T&);// error: incorrect placement of inline specifierinline template
T min(const T&, const T&);

 

类模板

类模板也是模板,因此必须以关键字 template 开头,后接模板形参表。Queue 模板接受一个名为 Type 的模板类型形参。

除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。

template 
class Queue {public:Queue (); // default constructorType &front (); // return element from head of Queueconst Type &front () const;void push (const Type &); // add element to back of Queuevoid pop(); // remove element from head of Queuebool empty() const; // true if no elements in the Queueprivate:// ...};

 

与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:

Queue
qi; // Queue that holds intsQueue< vector
> qc; // Queue that holds vectors of doublesQueue
qs; // Queue that holds strings

 

除了定义数据成员或函数成员之外,类还可以定义类型成员。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:

template 
Parm fcn(Parm* array, U value){typename Parm::size_type * p; // ok: declares p to be a pointer}

通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。

如果拿不准是否需要以 typename 指明一个名字是一个类型,那么指定它是个好主意。在类型之前指定 typename 没有害处,因此,即使 typename 是不必要的,也没有关系。

 

非类型模板形参

模板形参不必都是类型。模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。

// initialize elements of an array to zerotemplate 
void array_init(T (&parm)[N]){for (size_t i = 0; i != N; ++i) {parm[i] = 0;}}int x[42];double y[10];array_init(x); // instantiates array_init(int(&)[42]array_init(y); // instantiates array_init(double(&)[10]

 

类型等价性与非类型形参: 对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。array_init 调用引用的是相同的实例—— array_init<int, 42>:

int x[42];const int sz = 40;int y[sz + 2];array_init(x); // instantiates array_init(int(&)[42])array_init(y); // equivalent instantiation

 

在函数模板内部完成的操作限制了可用于实例化该函数的类型。程序员的责任是,保证用作函数实参的类型实际上支持所用的任意操作,以及保证在模板使用哪些操作的环境中那些操作运行正常。

 

编写独立于类型的代码的一般原则:编写模板代码时,对实参类型的要求尽可能少是很有益的。说明了编写泛型代码的两个重要原则:

• 模板的形参是 const 引用。

• 函数体中的测试只用 < 比较。

通过将形参设为 const 引用,就可以允许使用不允许复制的类型。大多数类型(包括内置类型和我们已使用过的除 IO 类型之外的所有标准库的类型)都允许复制。但是,也有不允许复制的类类型。将形参设为 const 引用,保证这种类型可以用于 compare 函数,而且,如果有比较大的对象调用 compare,则这个设计还可以使函数运行得更快。

 

实例化

模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程称为实例化。模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。

 

类模板的每次实例化都会产生一个独立的类类型。为 int 类型实例化的 Queue 与任意其他 Queue 类型没有关系,对其他Queue 类型成员也没有特殊的访问权。

 

从函数实参确定模板实参的类型和值的过程叫做模板实参推断。

 

类型形参的实参的受限转换

一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:

• const 转换:接受 const 引用或 const 指针的函数可以分别用非 const对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。

• 数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。

template 
T fobj(T, T); // arguments are copiedtemplate
T fref(const T&, const T&); // reference argumentsstring s1("a value");const string s2("another value");fobj(s1, s2); // ok: calls f(string, string), const is ignoredfref(s1, s2); // ok: non const object s1 converted to const reference int a[10], b[42];fobj(a, b); // ok: calls f(int*, int*)fref(a, b); // error: array types don't match; arguments aren't converted to pointers

 

模板实参推断与函数指针

可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。

template 
int compare(const T&, const T&);// pf1 points to the instantiation int compare (const int&, constint&)int (*pf1) (const int&, const int&) = compare;

获取函数模板实例化的地址的时候,上下文必须是这样的:它允许为每个模板形参确定唯一的类型或值。如果不能从函数指针类型确定模板实参,就会出错。

// overloaded versions of func; each take a different function pointer typevoid func(int(*) (const string&, const string&));void func(int(*) (const int&, const int&));func(compare); // error: which instantiation of compare?

 

在返回类型中使用类型形参

指定返回类型的一种方式是引入第三个模板形参,它必须由调用者显式指定:

// T1 cannot be deduced: it doesn't appear in the function parameterlisttemplate 
T1 sum(T2, T3);// ok T1 explicitly specified; T2 and T3 inferred from argument typeslong val3 = sum
(i, lng); // ok: calls long sum(int, long)

 

显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。

// poor design: Users must explicitly specify all three template parameterstemplate 
T3 alternative_sum(T2, T1);// error: can't infer initial template parameterslong val3 = alternative_sum
(i, lng);// ok: All three parameters explicitly specifiedlong val2 = alternative_sum
(i, lng);

 

模板编译模型

编译器实例化特定类型的模板的时候,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要那些通常放在源文件中的代码。标准 C++ 为编译模板代码定义了两种模型。

在包含编译模型中,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条 #include 指示使定义可用,该#include 引入了包含相关定义的源文件:

// header file utlities.h#ifndef UTLITIES_H // header gaurd (Section 2.9.2, p. 69)#define UTLITIES_Htemplate 
int compare(const T&, const T&);// other declarations#include "utilities.cc" // get the definitions for compare etc.#endif// implemenatation file utlities.cctemplate
int compare(const T &v1, const T &v2){if (v1 < v2) return -1;if (v2 < v1) return 1;return 0;}// other definitions

 

在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,可以使用 export 关键字来做这件事。对类模板使用 export 更复杂一些。通常,类声明必须放在头文件中

// class template header goes in shared header filetemplate 
class Queue { ... };// Queue.ccimplementation file declares Queue as exportedexport template
class Queue;#include "Queue.h"// Queue member definitions

导出类的成员将自动声明为导出的。也可以将类模板的个别成员声明为导出的,在这种情况下,关键字 export 不在类模板本身指定,而是只在被导出的特定成员定义上指定。

 

类模板的 static 成员

template 
class Foo {public:static std::size_t count() { return ctr; }// other interface membersprivate:static std::size_t ctr;// other implementation members};

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

 

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

Foo
fi, fi2; // instantiates Foo
classsize_t ct = Foo
::count(); // instantiates Foo
::countct = fi.count(); // ok: uses Foo
::countct = fi2.count(); // ok: uses Foo
::countct = Foo::count(); // error: which template instantiation?

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

 

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

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

 

一个泛型句柄类

/* generic handle class: Provides pointerlike behavior. Although access through* an unbound Handle is checked and throws a runtime_error exception.* The object to which the Handle points is deleted when the last Handle goes away.* Users should allocate new objects of type T and bind them to a Handle.* Once an object is bound to a Handle,, the user must not delete that object.*/template 
class Handle {public:// unbound handleHandle(T *p = 0): ptr(p), use(new size_t(1)) { }// overloaded operators to support pointer behaviorT& operator*();T* operator->();const T& operator*() const;const T* operator->() const;// copy control: normal pointer behavior, but last Handle deletes the objectHandle(const Handle& h): ptr(h.ptr), use(h.use){ ++*use; }Handle& operator=(const Handle&);~Handle() { rem_ref(); }private:T* ptr; // shared objectsize_t *use; // count of how many Handle spointto *ptrvoid rem_ref(){ if (--*use == 0) { delete ptr; delete use; } }}; template
inline Handle
& Handle
::operator=(const Handle &rhs){++*rhs.use; // protect against self-assignmentrem_ref(); // decrement use count and delete pointers ifneededptr = rhs.ptr;use = rhs.use;return *this;} template
inline T& Handle
::operator*(){if (ptr) return *ptr;throw std::runtime_error("dereference of unbound Handle");}template
inline T* Handle
::operator->(){if (ptr) return ptr;throw std::runtime_error("access through unbound Handle");} template
inline const T* Handle
::operator->() const{ if (ptr) return ptr;else throw std::logic_error("unbound Sales_item"); } template
inline const T& Handle
:: const{ if (ptr) return *ptr;else throw std::logic_error("unbound Sales_item");} class Sales_item {public:// default constructor: unbound handleSales_item(): h() { }// copy item and attach handle to the copySales_item(const Item_base &item): h(item.clone()) { }// no copy control members: synthesized versions work// member access operators: forward their work to the Handle classconst Item_base& operator*() const { return *h; }const Item_base* operator->() const{ return h.operator->(); }private:Handle
h; // use-counted handle};

 

模板特化

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

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

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

• 函数形参表;

• 函数体。

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

 

模板特化必须总是包含空模板形参说明符,即 template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显式指定模板实参:

// error: invalid specialization declarations// missing template<>int compare
(const char* const&,const char* const&);// error: function parameter list missingtemplate<> int compare
;// ok: explicit template argument const char* deduced from parameter typestemplate<> int compare(const char* const&, const char* const&);

 

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

 

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

 

普通作用域规则适用于特化

在能够声明或定义特化之前,它所特化的模板的声明必须在作用域中。类似地,在调用模板的这个版本之前,特化的声明必须在作用域中:

// define the general compare templatetemplate 
int compare(const T& t1, const T& t2) { /* ... */ }int main() {// uses the generic template definitionint i = compare("hello", "world");// ...}// invalid program: explicit specialization after calltemplate<>int compare
(const char* const& s1,const char* const& s2){ /* ... */ }

 

这个程序有错误,因为在声明特化之前,进行了可以与特化相匹配的一个调用。当编译器看到一个函数调用时,它必须知道这个版本需要特化,否则,编译器将可能从模板定义实例化该函数。

 

重载与函数模板

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

 

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

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

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

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

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

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

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

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

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

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

• 否则,调用有二义性。

 

设计既包含函数模板又包含非模板函数的重载函数集合是困难的,因为可能会使函数的用户感到奇怪,定义

函数模板特化几乎总是比使用非模板版本更好。

转载地址:http://xrkbx.baihongyu.com/

你可能感兴趣的文章
Linux yum和apt-get用法及区别
查看>>
python 中类中 __slots__
查看>>
C++ 随机数
查看>>
Linux常用命令笔记---计划任务
查看>>
配置Nutch模拟浏览器以绕过反爬虫限制
查看>>
如何使用netfilter/iptables构建防火墙
查看>>
livemesh在远程桌面中的运用
查看>>
蜜罐技术的配置模式和信息收集
查看>>
查看Oracle的实例名
查看>>
Zend Studio去除编辑器的语法警告
查看>>
linux驱动current关键词
查看>>
让SQL再快一点儿
查看>>
而尔维尔
查看>>
ios获取手机状态 idfa idfv 网络类型 分辨率 获取运营商 ip
查看>>
微信小程序下nginx代理wss,实现兼容原本服务协议ws,Java版本
查看>>
Linux RPS RFS
查看>>
通过Secure CRT导出设备配置
查看>>
我的友情链接
查看>>
jmeter从上一个请求使用正则表达式抓取Set-Cookie值,在下一个请求中运用
查看>>
【Mybatis框架】输入映射-pojo包装类型
查看>>