泛型程序设计与STL(简体版)

泛型程序设计与STL(简体版)
泛型程序设计与STL(简体版)

大局观:泛型程序设计( Generic Programming)与 STL 侯捷 jjhou@https://www.360docs.net/doc/dc18539411.html,.tw https://www.360docs.net/doc/dc18539411.html,

=> 1. 大局观:泛型程序设计与 STL 2. 泛型指标(Iterators)与

Traits 技术 3. 泛型容器(Containers)的应用与实作 4. 泛型算法( Generic Algorithms)与 Function Objects 5. 各色各样的 Adaptors 2000.02 2000.03 2000.04 2000.05 2000.06

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

面对 Generic Programming(泛型程序设计),我有相彷的感觉。

我将撰写为期五次的系列文章,为各位介绍这个不算新却又有点新的观念与技术。

●C++ Template, Generic programming, STL

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

撇开追求技术的热诚不谈(也许有人确实喜欢什么都自己来),商用链接库可以拯救这些程序员于水深火热之中。只要你的老板愿意花一点点钱,就可以在人力资源与软件开发效率上取得一个绝佳平衡。但是金钱并非万能,商用链接库彼此之间并无接口上的标准!换句话说你无法在发现了

另一套更好、更丰富、效率更高的链接库后,改弦更张地弃此就彼--那可能会使你付出惨烈的代价。这或许是封闭型技术与观念带给软件组件公司的一种小小的、短暂的利益保护。

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

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

完成工作。

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

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

针对目标物之不同,C++ 支持 function templates 和 class templates两大类型。后者的 members又可以是 templates(所谓 member templates),形成深

度巢状(nested),带来极大的弹性与组合空间。关于这点,本文稍后示范 STL 的使用时,会展现在你面前。

下面是一个简单的 function template:

#include using namespace std;

template 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) }

下面是一个简单的 class template:

template class Queue { public:

Queue() { /* ... */ }; ~Queue() { /* ... */ };

Type& remove(); void add( const Type & ); bool is_empty(); bool is_full();

private: // .... };

一旦程序开始使用class template Queue,例如:

Queue qi; Queue< complex > qc; Queue qs;

编译程序就会分别具现出针对 int, complex和 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使泛型技术有了理想的实现环境。「型别参数化」,噢,那不正是「泛型」吗 :)

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

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

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

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

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

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

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

●STL 的历史(注1)

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

要不失效率的一个重要关键。

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

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

Standard的一部份。

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

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

由于 STL 已被纳入 C++ Standard,成为C++ Standard Library的重要一员,因此目前各 C++ 编译程序都支持有一份 STL。

STL 在哪里?就在相应的各个 C++ 含入檔中,如 , ,, , …。这些未带 .h扩展名的C++ 含入檔,是

C++ Standard所规定的标准命名法。某些编译程序并没有完全遵循这样的命名规则,仍沿用旧有的 .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 实作品。下面是几个资源丰富的网站,可再从中往外连结:

https://www.360docs.net/doc/dc18539411.html,/Technology/STL/ https://www.360docs.net/doc/dc18539411.html,/

至于 STL 相关书籍,请见本期的【无责任书评】专栏。我一共介绍了四本书。

●学习 STL 的三个境界

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

第一个境界是使用 STL。

第二个境界是了解泛型程序设计的内涵与 STL 的实作原理。

第三个境界是扩充 STL。

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

STL

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

STL 有六大组件(components):

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

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

3. 3. iterator:应用于 containers 与 algorithms身上的所谓「泛型指标」,扮演两者间的胶着剂。共有五种型态,还有各种衍生变

化。iterator是 STL 中最重要最抽象的一个组件,它使containers 和algorithms可以各自独立发展,互不干连。

4. 4. function object:行为上类似「一整个函式」的一种组件。实作技术上则是一个改写了 "call operator"的 classes。C/C++ 的函式

指标,可以被用来作为一个 function object。STL 提供有 15个现成的function objects。

5. 5. adaptor:可改变(修饰)containers 或 function object接口的一种组件。例如 STL 提供的 queue 和stack,虽然看似泛型容

器,但它其实只是一种 container adaptor。用来改变function object接口者,称为 function adaptor(或称

functor)。用来改变 container 接口者,称为 container adaptor。用来改变iterator接口者,称为 iterator adaptor。

6. 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++ Standard Library的所有组件均封闭于一个名为std 的namespace,所以使用前必须先使其曝光。最简单的作法就是撰写 using directive如下:

using namespace std;

图一\使用各种 STL 组件时,程序所需要的含入档

STL 组件程序所需含入文件

algorithms

四个数值相关算法 (注 2)

vector

list

deque

stack

queue

priority queue

map

set

multimap

multiset

function objects

iterator adaptor

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

以下动作完成一个数组:

int ia[] = { 1, 3, 2, 4 };

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

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

list ilist(ia, ia+4); vector ivector(ia, ia+4); deque ideque(ia, ia+4); stack

istack(ideque); queue iqueue(ideque); priority_queue ipqueue(ia, ia+4); set iset(ia, ia+4);

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

map 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 objects可以提供,所以我自己设计一个函式来完成。

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::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,都定义有适合它自己所用的 iterators。下面是 vector的 iterator定义方式:

vector::iterator iter;

vector::const_iterator citer;

下面是 list的 iterator定义方式:

list::iterator iter; list::const_iterator citer;

以下使用 STL algorithm accumulate(),对 ilist做累积动作,并指定所谓「累积动作」是指乘法。我

采用 STL function objectmultiplies完成乘法动作。

// 以下行为,造成 1 * 3 * 2 * 4. cout <<

accumulate(ilist.begin(), ilist.end(), 1, multiplies()) <<

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

宣告一个 list,每个元素是个 vector,后者的每个元素是个 pair。pair有两个元素,第一元素是个 string,第二元素是个 int:list < vector < pair < string, int > > > mylist;

下面是 generic algorithms for_each()、function object modulus、function adaptor bind2nd()、container list的运用实例:

// BCB4 : bcc32 test.cpp // GCC : g++ -o test.exe test.cpp #include #include #include #include using namespace std;

template // 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 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(), 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

#include #include #include #include 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;

}

// 宣告三个 iterators, // 一个针对档案,输入用,输入单位是 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的一个 model。concept可被视为是一组型别条件。如果 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. 1. Reflexivity反身性。每一个 concept C是其本身的一个 refinement。

2. 2. Containment 涵盖性。如果 type X是 concept C2的一个 model,而 C2是concept C1 的一个refinement,那么 X 必然是

C1的一个 model。

3. 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 原始码的重要关键。至于iterators,是使数据结构(STL containers)与算法(STL algorithms)相互独立发展又可交互搭接的关键,其重要性与关键性居 STL 六大组件之首。

参考数据:

1. 1. "Alexander Stepanov and STL", Dr. Dobb's Journal, Mar. 1995.

2. 2. [Austern99] "Generic Programming and the STL" by Matthew H. Austern, AW, 1999

3. 3. [Lippman98] "C++ Primer" by Lippman & Lajoie, AW, 1998

4. 4. [Musser96] "STL Tutorial and Reference Guide" by David R. Musser, AW, 1996

作者简介:侯捷,信息技术自由作家,专长 Windows操作系统、SDK/MFC 程序设计、C/C++ 语言、面向对象程序设计、泛型程序设计。目前在元智大学开授泛型程序设计课程,并进行新书《泛型程序设计》之写作。

相关主题
相关文档
最新文档