大局观:泛型程序设计

 
大局观:泛型程序设计(Generic Programming 与 STL )1 候捷 Hover译简


十年前赶上OO(Object Oriented 面向对象)第一波工业浪潮的朋友们,想必今日有一番顾盼豪情,以及一份「好加在」的惊悚:「好加在」搭上了 OO 的早班列车,不然今天就玩不下去了!


面对 Generic Programming (泛型程序设计),我有相同的感觉。
我将撰写为期五次的系列文章,为各位介绍这个不算新却又有点新的概念与技术。


●C++ Template, Generic programming, STL


1968 Doug McIlroy 发表其著名论文 "Mass Produced Software Components",揭示了以「可重用软件组件构筑标准程序库的愿景。如今三分之一个世纪已逝,components software -- 以组件为基础的软件 -- 大行其道。然而在许多领域中,标准化仍未建立。甚至连任何软件一定会用到的基本数据结构和基本算法,对此亦付阙如。于是大量的程序员,被迫进行大 量重复的工作,为的竟只是重复完成前人早已完成而自己手上并未拥有的代码。这不但是人力资源的浪费,也是挫折与错误的来源。


撇开追求技术的热诚不谈(也许有人确实喜欢什么都自己来),商用程序库可以拯救这些程序员于水 深火热之中。只要你的老板愿意花一点点钱,就可以在人力资源与软件开发效率上取得一个绝佳平衡。 但是金钱并非万能,商用程序库彼此之间并无接口上的标准!换句话说你无法在发现了另一套更好、 更丰富、效率更高的程序库后,改弦更张地弃此就彼 -- 那可能会使你付出惨烈的代价。这或许是封 闭型技术与概念带给软件组件公司的一种小小的、短暂的利益保护。


除了「接口无标准」这一问题,另一个问题是,目前绝大部份软件组件都使用面向对象技术,大量运用继承与虚拟函数,导至运行成本增加。此外,一旦运用面向对象技术,我们此刻所说的基础数据结构及各种基础算法,便皆需以 container(容器,放置数据的某种特殊结构)为本。数据,放在容器类中(形成其data members;操作行为,亦定义在容器类中(形成其member functions) ;这使得耦合(coupling 两物过度相依而不独立)的情况依然存在,妨碍了组件之所以为组件的独立性、灵活性、通用性 。


换句话说,要解决这些困境,第一需要标准接口的建立,第二需要OO以外的新技术,俾能够比 OO 更高效率地完成工作。



◎新技术


C++ template 是新技术的曙光。


一言以蔽之,所谓 template 机制就是,将标的物的数据类型参数化。一旦程序以指定引用数据类型的方式, 确定了这些类型,编译器便自动针对这些类型产生出一份实例。这里所说的实例,可以是一个 function body 也可以是一个 class body而「由编译器产生出一份实例」的动作,我们称之为实例化(instantiation )。


针对标的物之不同,C++ 支持函数模板 和类模板 两大类型。类模板的 members 又可以 是 templates 所谓 member templates ,形成嵌套状(nested ),带来极大的灵活性与组合空间。关于这一点,本文稍后示范 STL 的使用时,会展现在你面前。


下面是一个简单的函数模板

#include <iostream>
using namespace std;
template <class Type>
Type mymin(Type a, Type b)
{
return a < b ? a : b;
}


由于 mymin() 内对于函数变量 a 和 b 动用到 less-than operator 所以任何类型如果希望能够满足这
个函数的 Type 必须支持 operator<。下面是个例子:


class rect
{
friend ostream& operator<<(ostream& os, const rect& rhs);
public:
rect(int w, int h) : _w(w), _h(h)
{ _area = _w * _h; }

bool operator<(const rect& rhs) const
{ return _area < rhs._area; }
private:
int _w, _h, _area;
};

ostream& operator<<(ostream& os, const rect& rhs)
{
os << '(' << rhs._w << ',' << rhs._h << ')' << endl;
return os;
}


int main()
{
// 此行实现出函数实体 int mymin(int, int);
cout << mymin(10, 20) << endl; // 10
// 此行实现出函数实体 double mymin(double, double);
cout << mymin(30.0, 20.0) << endl; // 20
rect r1(3,5), r2(5,7);
// 此行实现出函数实体 rect mymin(rect, rect);
cout << mymin(r1, r2) << endl; // (3,5)
}


下面是一个简单的 类模板
template <typename Type>
class Queue {
public:
Queue() { /* ... */ };
~Queue() { /* ... */ };
Type& remove();
void add( const Type & );
bool is_empty();
bool is_full();
private:
// ....
};
一旦程序开始使用 类模板 Queue 例如:
Queue<int> qi;
Queue< complex<double> > qc;
Queue<string> qs;
编译器就会分别实现出针对 int, complex<double> 和 string 的 Queue class 实例出来。


(关于 template 的语法与技术,请参考任何一本「年轻的」C++ 书籍。我推荐你 看 C++ Primer 3/e,by Lippman & Lajoie 第 10 章和第 16 章)


由于实例化行为是在编译时期完成,所以愈是复杂的 templates 愈会耗损编译时间。然而这却无损程序的运行时间。因此,对于组件的运行效率,带来一种保障。换言之,使用 template 并不必然会降低组件的运行效率(但对组件的开发效率则有显著的提升)。事实上,Alexander Stepanov (STL 的创造者)非常重视 STL 的运行效率,并视之为一种「基本国策」。他说过,以数据类型 stack为例,并不是拥有 push 和 pop 两个操作行为,就是一个好的 stack最重要的是,pushing 动作应该耗用固定时间,不因 stack 的大小而改变。如果我的 stack 的 pushing 动作愈来愈慢,没有人会用我这个 stack.


软件的最高目标,就是能够设计出一些可重复使用(reusable)的代码。所谓可重复使用,不仅意味可以像乐高(Lego) 积木那样广泛地加以配接组合(没有如此的灵活性又怎称得上重复使用?),而且在配接组合的过程中不会耗损其效率表现。效率议题,大概是你在明了 STL 的强大威力与灵活性之后,让你踌躇不前的唯一可能因素。然而我可以告诉你,使用 STL 并不会造成效率下降。STL 的效率表现,是经过严格规范与实现后的一个保证,而不是一种臆测。


C++ template 使泛型技术有了理想的实现环境。「数据类型参数化」,噢,那不正是「泛型」吗 Smile



◎新标准


泛型(Generic Genericity) 是一种概念,就好像面向对象(Object Oriented )是一种概念。 STL 标准 Template 库 则是一个以泛型技术完成的作品,就好像 MFC (Microsoft Foundation Classes)是一个以面向对象技术完成的作品。


前面曾经说过,在许多应用领域之中,标准化尚未建立。甚至连需求最殷的基本数据结构和基本演算法,亦无所谓的标准接口。


STL 成功地扮演了这方面(基本数据结构和基本算法)的标准接口角色。


乍见之下,STL 的价值在于其所带来的一套极具实用价值的组件(components 稍后会详加展示)。这种价值就像 MFC 或 VCL 之于Windows 软件开发过程所带来的价值一样,直接而明了。然而,STL 更有不同。STL 最重要的价值,其实是在泛型思维模式(Generic Paradigm )下建立起一个系统化的、条理分明的「软件组件分类学。这个分类学告诉我们什么 是 requirements 什么 是 concepts 什么是 models 什么 是 refinements目前没有任何电脑语言对于这些概念有实质的对应(Alexander Stepanov STL 的创造者,曾经说他的下一步就是要发展一个这样的语言)。


换句话说,STL 所实现的,是依据泛型思维模式而建构的一个概念结构。这个以 concepts(概念)为主体而非以classes为主体的概念结构,严谨地形成了一个接口标准。在此接口之下,任何组件有最大的独立性;组件之间以 迭代器(iterator)s胶合起来,或是以配接器(adaptors)互相配接,或是以函数对象(function object)传递一整组运算需求。


当然啦,不是所有的组件都可以互相配接。它们必须是可配接的(adaptable ),否则会损及效率,甚至根本配接不起来。


上述这些看似极度抽象的话语,也许使初初接触STL的你如坠云里雾里。当你逐渐从 STL 的实用面,到 STL 的实作面,到 STL 的扩展面,全面浸淫于 STL 之后,请你再回头来细想我的这些描述。


●STL 的历史(注1)


STL 系由 Alexander Stepanov 创造于 1979 年前后,这也正是Bjarne Stroustrup 创 造 C++ 的年代。 虽然 David R. Musser 于 1971 开始即在计算机几何领域中发展并倡导某些泛型程序设计概念,但早期并没有任何程序语言支持泛型程序设计。第一个支持泛型概念的语言是Ada Alex 和 Musser 曾于 1987 开发出一套相关的 Ada 库然而 Ada 在美国国防工业以外并未被广泛接受,C++ 却如星火燎原般地在程序设计领域中攻城略地。当时的 C++ 尚未导入 template 性质,但 是 Alex 已经了解到,C/C++ 允许程序员经由指针,以非常灵活性的方式处理内存,而这正是又要泛型又要不失效率的一个重要关键。


更重要的是,必须研究并实验出一个「基于泛型程序设计之上的 component 库 的完整架构」。 Alex 在 AT&T 实验室及Hewlett-Packard Palo Alto 实验室,分别实验许多种架构和算法公式,先以 C 而后以 C++ 完成。1992 年 Meng Lee 加入 Alex 的专案,成为另一位主要贡献者。

Bell 实验室的 Andrew Koenig 于 1993 年知道这个研究计划后,邀请 Alex 于是 年 11 月的NSI/ISO C++ 标准委员会会议上展示其概念。获得热烈的回应。Alex 于是再接再励于次年夏天在
Waterloo 举行的会议前完成其正式的提案,并以压倒性多数,一举让这个巨大的计划成为 C++ 标准
的一部份。


注1 如欲更清楚知道 STL 的历史,请参 考 Dr Dobb's Journal 于 1995 年三月刊出的 "Alexander
Stepanov and STL" 一文。




●实作品、编译器、网络资源、书籍


由于 STL 已被纳入 C++ 标准 成 为 C++ 标准库 的重要一员,因此目前各 C++ 编译器都支持 STL.


STL 在哪里?就在相应的各个 C++ 头文件中,如 <vector>, <list>,<functional>, <algorithm>, <迭代器(iterator)>…。这些未带 .h 文件名的C++ 头文件,是 C++ 标准 所规定的标准命名法。某些编译器并没有完全遵循这样的命名规则,仍沿用旧的 .h 命名法,例如Inprise C++Builder但是你仍然可以在程序中使用上述无.h 扩展名的头文件,原因是这类编译器的前置处理器对此做了一些手脚。

大部份 C++ 编译器都提供 (1) 有 .h 文件名,(2) 没有任何文件名的两套头文件。例如 Visual C++无 .h 文件名者含入有 .h 文件名者,来涵盖早先的编码习惯。又例如 GNU C++则是以几乎相的两份头文件(一个无 .h 文件名,一个 有 .h 文件名),再于其中含入名 为 stl_xxx.h 的实际主角。

以下是我手上三套 C++ 编译器内含的 STL 实作品的供应者:
Microsoft VC6 P.J. Plauger
Inprise C++Builder4 Rogue Wave Software, Inc.
GNU C++ egcs-2.91.57 Silicon Graphics Computer Systems, Inc


所有 STL 实作品的老祖宗,都是源于 Hewlett-Packard Company 并由 Alexander Stepanov 和 Meng Lee 完成的原本。其中任何一个文件上头,都有一份声明,允许你任意使用、拷贝、修改、传播、贩卖这些码,无需付费,但必须将该份声明置于你的文件内。


你还可以从网络下载其他的 STL 实作品。下面是几个资源丰富的网站,可再从中往外链接:
http://www.sgi.com/Technology/STL/
http://www.stlport.org/
至 于 STL 相关书籍,请见本期的【无责任书评】专栏。我一共介绍了四本书。


●学习 STL 的三个境界


王国维说大事业大学问者的人生有三个境界。我认为学习泛型程序设计以 及STL也有三个境界:


第一个境界是使用 STL
第二个境界是了解泛型程序设计的内涵与 STL 的实现原理。
第三个境界是扩充 STL


应该还要有个第0境界,或说学习泛型程序设计与 STL 的门槛,那就 是 C++ template 机制。



◎境界一:使用 STL


对程序员而言,诸多抽象描述,不如实际的 code 可以直指人心。稍后我直接列几段程序码,你就大略知道 STL 的威力了。


STL 有六大组件(components ):


1. 容器(containers) 泛型容器,各种基本数据结构 如 vector, list,deque, set,map。


2. 泛型算法(algorithms) 泛型算法,各种基本算法 如 sort, search,copy, erase STL 提供了70个。


3. 迭代器(iterator) 应用于 容器(containers) 与 algorithm 身上的所谓「迭代器」,扮演两者间的胶著剂。共有五种型态,还有各种衍生变化。迭代器(iterator)是STL中最重要最抽象的一个组件,它使容器(containers)和泛型算法(algorithms)可以各自独立发展,互不干连。


4. 函数对象(function object) 行为上类似「一整个函数」的一种组件。实际技术上则是一个改写了"call operator"的classesC/C++的函数指针,可以被用来做为一个函数对象(function object) STL提供有15个现成的 函数对象(function object).


5. 配接器(adaptor) 可改变容器(containers)或函数对象(function object)接口的一种组件。例如STL提供的queue和stack 虽然看似泛型容器,但它其实只是一种容器配接器(container adaptor)用来改变函数对象(function object) 接口者,称为函数配接器(function adaptor) 或称 functor 。用来改变容器(container)接口者,称为容器配接器(container adaptor).用来改变迭代器(iterator) 接口者,称为 迭代器配接器(iterator adaptor).


6. allocator 内存分配系统。STL 提供有现成的 allocator



◎境界二:了解 generic programming的内涵与STL的实现原理


知道怎么运用 STL 之后,有了点成就感,对实际工作也贡献了一些,可以开始比较不那么现实(但其实仍能回馈到现实面)的深钻功夫了。


最好能对一两个(不必太多)STL组件做一番深刻追踪。STL源代码都在手上(就是相应的那些头文件嘛),好好认识并体会一下 STL 所谓的 Concepts 和 Modeling 和 Refinement 好好了解一下为什么需要所谓的 traits 技术(这一技术非常重要,大量运用 于 STL 之中,我将在第二篇文章详细解释其内涵)。好好做几个项目研究,便能够对泛型程序设计以 及 STL 有理论上的通盘掌握。



◎境界三:扩充 STL


最高境界,当然是 在 STL 不能满足你的时候,自己动手写一个可融入STL体系中的软件组件了。这当然得先彻底了解 STL 也就是得通过上述第二境界的痛苦折磨。




●STL 运用实例


对世人而言,1815 年的滑铁卢(waterloo in Belgium )标示了失败的印记。但对 C++ 社群而言,1994
年的滑铁卢(waterloo in Ontario, USA) 标志着的则是极大的成功。这一年于该处举行之C++ 标准委员
会,正式将 STL 纳入。


STL 是泛型程序设计的一个研究成果。它的内涵或许有相当的难度,但是它的运用却是极其简单而又令人愉悦的。下面就是几个简单的实例。我在程序中先制作出一个数组,再以此为初值,放进各种 STL 容器(containers) 之中(各 个 STL 容器(containers) 的特性,将在本系列第三篇文章中说明)。然后我示范如何使用 STL 泛型算法(algorithms) for_each()。注意,STL 容器(containers) 以放置各种类型,包括使用者自定的class 类型;为求简易,我一概以 int 类型为例。

图一是使用各种 STL 组件时,程序所需要的头文件。使用 STL 时必须注意,由于 C++ 标准
库 的所有组件均封闭于一个名 为 std 的 namespace 所以使用前必须先使其曝光。最简单的作
法就是撰 写 using directive 如下:

using namespace std;

图一/用各种 STL 组件时,程序所需要的头文件

STL 组 件  程序所需头文件
泛型算法(algorithms) <algorithm>
四个数值相关算法 <numeric>(注2)
vector <vector>
list  <list>
deque <deque>
stack <stack>
queue <queue>
priority queue <queue>
map  <map>
set  <set>
multimap  <map>
multiset <set>
函数对象(function object)s  <functional>
迭代器(iterator) 配接器(adaptor) <iterator>

注2 四个数值相关算法是:accumulate(), adjacent_difference(), partial_sum(), inner_product().



以下动作完成一个数组:
int ia[] = { 1, 3, 2, 4 };


以下各动作将此数组分别当做各个容器(containers) 的初值。每一个容器(containers) 通常都拥有数个版本的构造函数,提供很大的灵活性让我们设定初值。一般而言不外乎指定另一个容器(container)做为初值,或是指定某个容器(container)的某个范围做为初值。运用「范围」这个概念时,就要用到迭代器(iterator);此处由于所处理的是简单的 int 类型,所以 C++ 原始指针可以拿来当做迭代器(iterator) 使用。


请注意,各家编译器对 C++ 标准 的支持程度不一。以下程序可在 Inprise C++Builder 4.0 顺利编译完成。

list<int> ilist(ia, ia+4);
vector<int> ivector(ia, ia+4);
deque<int> ideque(ia, ia+4);
stack<int> istack(ideque);
queue<int> iqueue(ideque);
priority_queue<int> ipqueue(ia, ia+4);
set<int> iset(ia, ia+4);


注意,map 容器放置的是一对一对的(键值/值),所以我以这样的方式安排其初值:


map<string, int> simap; // 以 string 为键值(索引),以 int 为实值
simap[string("1")] = ia[0]; // 第一对内容 是 ("1", 1)
simap[string("2")] = ia[1]; // 第二对内容 是 ("2", 3)
simap[string("3")] = ia[2]; // 第三对内容 是 ("3", 2)
simap[string("4")] = ia[3]; // 第四对内容 是 ("4", 4)



以下动作分别将各个 容器(containers) 的内容显示出。其中用 到 for_each(),是一个 STL 泛型算法(algorithms) 要求使用者指定一个范围,以及一个「动作」。范围可以(应以)迭代器(iterators) 表示。每一种容器(containers)乎都提供有begin()和end()两个member functions分别传回指向头部和指向尾部的两个迭代器(iterators)标 示出整个容器(container)。未提供begin()和end()者,我改以while回圈来进行巡访,而不使用for_each()。


至于巡访获得一个元素之后所要运行的「动作」,可以函数指针或STL 函数对象(function object)表示。我希望对每一个元素施行的「动作」是将元素内容丢到标准输出设备 cout 这样的「动作」并没有任何 STL 函数对象(function object) 可以提供,所以我自己设计一个函数来完成。

for_each(ilist.begin(), ilist.end(), pfi); // 1 3 2 4
for_each(ivector.begin(), ivector.end(), pfi); // 1 3 2 4
for_each(ideque.begin(), ideque.end(), pfi); // 1 3 2 4
for_each(iset.begin(), iset.end(), pfi); // 1 2 3 4 排序)
while(!istack.empty())
{
cout << istack.top() << " "; // 4 2 3 1 先进后出)
istack.pop();
}
while(!iqueue.empty())
{
cout << iqueue.front() << " "; // 1 3 2 4 先进先出)
iqueue.pop();
}
while(!ipqueue.empty())
{
cout << ipqueue.top() << " "; // 4 3 2 1 按优先权次序取出)
ipqueue.pop();
}

请注意,stack(先进后出),queue(先进先出),priority queue (依优先权次序决定「下一个」是谁), 以及set(内部有排序行为)的输出结果,在这都显示出其特性。

map 的处理稍稍不同。由于map容器所放的数据都是一对一对的「键值/值(key/value) 」,所当我们巡访到一对数据时,必须以first和second来取出其第一份内容(键值)和第二份内容(实 值):

map<string, int>::iterator iter; // 迭代器(iterator) 的类型必须先指定清楚
for (iter=simap.begin(); iter!=simap.end(); ++iter)
{
cout << iter->first << " " << iter->second << " "; // 1 1 2 3 3 2 4 4
}

每一种容器(containers)定义有适合它自己所用 迭代器(iterator)s下面 是 vector<int>的迭代器(iterator)定义方式:

vector<int>::iterator iter;
vector<int>::const_iterator citer;


下面 是 list<string> 的 迭代器(iterator) 定义方式:
list<string>::iterator iter;
list<string>::const_iterator citer;

以下使用 STL algorithm accumulate(),对 ilist 做阶乘动作,并指定所谓「阶乘动作」是指乘法。我
采用 STL 函数对象(function object)multiplies<int> 完成乘法动作。

// 以下行为,造成 1 * 3 * 2 * 4.
cout << accumulate(ilist.begin(), ilist.end(),1, multiplies<int>()) << endl; // 24

以下使用 STL algorithm inner_product(),对 ilist 和 iset 进行内积行为。

// 以下行为,造成 0 + 1*1 + 3*2 + 2*3 + 4*4.
cout << inner_product(ilist.begin(), ilist.end(), // 第一范围的头尾
iset.begin(), // 第二范围的起始点
0 ) // 运算初值
<< endl; // 29



●STL 灵活性演示


以下试举数例。可略窥 STL 的灵活性。

声明一 个 list 每个元素又是个 list 后者的每个元素是个字串:
list < list <string> > mylist;

声明一 个 list 每个元素是 vector 后者的每个元素是 个 pairpair 有两个元素,第一元素是个 string,第二元素是 个 int

list < vector < pair < string, int > > > mylist;


下面 是 泛型算法(algorithms) for_each()、函数对象(function object) modulus<int>、function 配接器(adaptor) bind2nd()、容器(container) list<int> 的运用实例:

// BCB4 : bcc32 test.cpp
// GCC : g++ -o test.exe test.cpp
#include <functional>
#include <list>
#include <iostream>
#include <algorithm>

using namespace std;
template <typename T> // function template
void print_elements(T elem)
{ cout << elem << " "; }

void (*pfi)(int) = print_elements; // 函数指针

void main()
{
int ia[7] = {0,1,2,3,4,5,6};
list<int> ilist(ia, ia+7); // 以数组做为 list 的初值
for_each(ilist.begin(), ilist.end(), pfi); // 0 1 2 3 4 5 6
ilist.push_back(7);
ilist.push_back(0);
ilist.push_back(7);
ilist.push_back(9);
for_each(ilist.begin(), ilist.end(), pfi); // 0 1 2 3 4 5 6 7 0 7 9
ilist.remove_if(bind2nd(modulus<int>(), 2)); // 删除所有奇数
for_each(ilist.begin(), ilist.end(), pfi); // 0 2 4 6 0
}

下面程序从文件中读取所有单字,再全部列出于屏幕上。

// CB4 : bcc32 test.cpp
// VC6 : cl -GX test.cpp
// GCC : g++ -o test.exe test.cpp
#include <iostream>
#include <string>
#include <algorithm>
#include <fstream>
#include <iterator>

using namespace std;
int main()
{
string file_name;
cout << "please enter a file to open: ";
cin >> file_name;
if ( file_name.empty() || !cin )
{ // 测试档名
cerr << "unable to read file name/n";
return -1;
}
ifstream infile( file_name.c_str());
if ( ! infile )
{ // 测试开档成功否
cerr << "unable to open " << file_name << endl;
return -2;
}

// 声明三 个 迭代器(iterator)s
// 一个针对文件,输入用,输入单位 是 string
// 一个针对文件,代表 end-of-stream
// 一个针对 cout 输出用,输出单位 是 string 后面紧跟一 个 " "
istream_iterator< string > ins( infile ), eos;
ostream_iterator< string > outs( cout, " " );
copy( ins, eos, outs ); // 把文件内容 copy 到屏幕上
}

以上这些例子,有些东西及其对应动作并不能让你有直接的想像,尤其是stream 迭代器(iterator)但是只要花 一点时间,了解它们的规格与用途,使用起来非常简单方便。



●软件组件分类学


前面说过,STL 其实是在泛型思维模式之下建立起一个系统化的、条理分明的「软件组件分类学」。这个分类学严谨定义了什么是concept, 什么是 model 什么是 refinement, 什么是 range 也定义了什么是 predicate, 什么是 迭代器(iterator), 什么 是 配接器(adaptor)...


我将在此描述其中一二,试图让你在最短篇幅中对STL本质建构出一个基本的认识。你可以参考 [Austern99],获得更丰富更详细的说明。



◎concept, model


所谓concept描述某个抽象类型的一组条件(或说需求,requirements)。concept并不是一 个 class也不是一个变量或是一 个 template 参数;事实 上 C++ 程式中没有任何东西可以直接代表一个concept然而,在每一个用到泛型程式设计方法 的 C++ 程式中,concept 非常重要。由 concepts 所构成的阶层体系,正是 STL 的主体概念结构。

当某个类型满足所有条件,我们便说此类型是该 conecpt 的一个 modelconcept 可被视为是一组类型件。如果 type T 是 concept C 的一 个 model 那么 T 就一定满 足 C 的所有条件(需求)。因 此,concept 亦可被视为是一 组 types例如 concept Input 迭代器(iterator) 可以由char*, int*, float* 及其他符合条件 的 wrap classes共同组成。如果 type T 是 concept C 的一个 model 那么我们可以说 T 隶属于「C 所表现的一组类型」。

concepts发现与描述,并不是藉由写下某些需求条件而达成,而是由于我们定义了明确的泛型算法(algorithms) 并学习如何在它们身上使用formal template arguments于是逐步完成。换句话说这是一个反覆修润 的过程,不是纸上单向作业。


STL 所规范的基本 concepts 包括:

1. Assignable type X如果是concept Assignable的一个model那么我们可以将type X 的 object内容拷贝并指派给 type X 的另一 个 object如果 x, y 是 Assignable 那么保证以下动作中 的 x, y 有著相同的值:

X x(y);
x = y;
tmp = y, x = tmp;

换言之,如果 type X 是 Assignable 的一 个 model 它将会有一 个 copy constructor.



2. Default Constructible如果type T 是 Default Constructible 的一个model它将有一个default
constructor也就是说我们可以这样写,产生出一 个 type T object

T()
欲产生出一 个 type T 的变量,可以这样写:
T t;
所有 的 C++ 标准类型如 int 和 void 都隶属于 Default Constructible



3. Equality Comparable 如果 type T是Equality Comparable的一个model那么我们可以这样比较两个 type T objects 是否相等:
x==y

x!=y



4. LessThan Comparable如果type T是LessThan Comparable的一个model我们可以这样测试某个T object 是否小于另一 个 T object:
x < y

x > y

所 谓 regular type是指同时身为Assignable, Default Constructible,Equality Comparable等 concepts 的 model针对一 个 regular type 你可以这样想:如果你将 x 指派(assign 给 y 那么 x==y 一定为真。


大部份 basic C++ types 都是regular types int是本节谈及的所有concepts 的一个model,而几乎所有定义于 STL 中 的 types也都是 regular types




◎refinements


如果 concept C2供应concept C1的所有机能,并且(可能)加上其他机能,我们便 说 C2 是 C1 的一个 refinement 精练品)。


Modeling 和 refinement 必须满足以下三个重要特性。只要把 concepts 想像为一组 types 以下三者就很容易验证:

1. Reflexivity 反身性。每一 个 concept C 是其本身的一 个 refinement.
2. Containment 涵盖性。如果type X是concept C2的一个model而C2是concept C1的一个refinement那么 X 必然 是 C1 的一个model.
3. Transitivity 递移性。如果 C3 是 C2 的一 个 refinement 而C2是C1的一个 refinement那么 C3 是 C1 的一 个 refinement.

一 个 type 可能是多 个 concepts 的 model 而一 个 concept 可能是多 个 concept 的 refinement




◎range (范围)


对于range[first,last),我们说,只有当[first,last)之中的所有指针都是可提取的(dereferenceable,
而且我们可以从first到达last(也就是说对first累加有限次数之后,最终会到达 last),我们才说 [first, last) 是有效的。所以,[A, A+N) 是一个有效的 range empty range [A, A) 也是有效的。[A+N, A)就不是一个有效的 range.


一般而言,ranges 满足以下性质:

1. 对任何指针 p 而言,[p, p) 是一个有效的 range 代表一个空范围。
2. 如果[first,last)是一个有效而且非空的range那么[first+1,last)也是一个有效的range
3. 如果[first,last)是一个有效的range而且mid是一个可由first前进到达的指针,而且last又可以由 mid 前进到达,那么 [first, mid) 和 [mid, last) 都是有效的 ranges.
4. 反向来讲,如果[first, mid)和[mid, last)都是有效的ranges那么[first, last)便是有效的ranges






下一期,我要带各位看 看迭代器(iterator)的实作以及所谓的traits技术。Traits 技术几乎于STL的每一个地方出没,至为重要。了解它的发展原由与最后形象,是掌 握STL源代码的重要关键。至于迭代器(iterator)s是使数据结构(STL 容器(containers)与算法(STL 泛型算法(algorithms)相互独立发展又可交互搭接的关键,其重要性与关键性居STL六大组件之首。



 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值