本书全面展示如何使用C++编写可重用的代码,从而提高程序员的开发效率。\r\n 全书分为12章。包括重用性基本概念、类设计、扩展性、效率、错误、冲突、兼容性、继承、移植性、程序库等和重用相关的诸多话题。每一章的最后,通过总结和练习帮助你巩固概念、加深理解,参考文献和相关资料为你指明了深入学习的方向。\r\n 本书适合有一定C++经验的程序员阅读,也可供以提高代码重用性为专门学习方向的读者参考。
第1章 重用性介绍\r\n\r\n1.1 什么是重用性\r\n1.1.1 提取代码来作为重用\r\n1.1.2 可重用代码的基本特性\r\n1.2 重用的神话\r\n1.3 重用的障碍\r\n1.3.1 非技术障碍\r\n1.3.2 技术障碍\r\n1.4 希望是否尚存\r\n1.5 这本书能给我们带来什么\r\n1.6 练习\r\n1.7 参考文献和相关资料\r\n\r\n第2章 类的设计\r\n\r\n2.1 抽象性\r\n2.2 正规函数\r\n2.3 Nice类\r\n2.4 存在最小标准接口吗\r\n2.4.1 缺省构造函数\r\n2.4.2 赋值运算符\r\n2.4.3 拷贝构造函数\r\n2.4.4 相等运算符\r\n2.4.5 析构函数\r\n2.5 浅拷贝和深拷贝\r\n2.6 接口一致性\r\n2.7 转型\r\n2.7.1 多重所有权(Multiple Ownership)\r\n2.7.2 敏感转型\r\n2.7.3 不敏感转型\r\n2.7.4 转型数目(Fanout)\r\n2.8 const关键字的使用\r\n2.8.1 抽象const对比位元const\r\n2.8.2 最大限度地使用const\r\n2.8.3 对const不安全的解释\r\n2.9 总结\r\n2.1 练习\r\n2.11 参考文献和相关资料\r\n\r\n第3章 扩展性\r\n\r\n3.1 扩展性的权衡\r\n3.2 扩展性和继承\r\n3.2.1 只继承基类的接口\r\n3.2.2 只继承基类的实现\r\n3.2.3 同时继承基类的接口和实现\r\n3.3 继承语义(Semantic)\r\n3.4 继承的障碍\r\n3.4.1 非虚成员函数\r\n3.4.2 过度保护\r\n3.4.3 模块化不足\r\n3.4.4 friend关键字的使用\r\n3.4.5 成员变量过多\r\n3.4.6 非虚(Nonvirtual)派生\r\n3.4.7 妨碍继承的成员函数\r\n3.5 派生赋值问题\r\n3.6 允许入侵(用户修改源代码)继承\r\n3.7 总结\r\n3.8 练习\r\n3.9 参考文献和相关资料\r\n\r\n第4章 效率\r\n\r\n4.1 效率和重用性\r\n4.2 程序创建时间\r\n4.2.1 编译时间\r\n4.2.2 实例化时间\r\n4.3 代码大小\r\n4.3.1 源文件分割\r\n4.3.2 外联的(outlined)inline\r\n4.3.3 模板特化大小\r\n4.4 运行时间\r\n4.4.1 内联(inlining)\r\n4.4.2 虚函数\r\n4.4.3 返回引用\r\n4.5 空闲存储空间(free-store)和堆栈空间(stack space)\r\n4.5.1 使用高效的算法\r\n4.5.2 尽可能快地释放空闲资源\r\n4.5.3 静态对象\r\n4.5.4 庞大的对象\r\n4.6 效率的权衡\r\n4.6.1 实现更加困难\r\n4.6.2 使用更加困难\r\n4.7 总结\r\n4.8 练习\r\n4.9 参考文献和相关资料\r\n\r\n第5章 错误\r\n\r\n5.1 可重用代码中的错误\r\n5.2 错误检测\r\n5.2.1 函数前提条件\r\n5.2.2 表示不变性\r\n5.3 处理错误\r\n5.3.1 程序库变量\r\n5.3.2 解决问题\r\n5.3.3 程序退出或者程序中止(Exit or Abort)\r\n5.3.4 抛出异常\r\n5.3.5 返回错误值\r\n5.3.6 创建Nil值\r\n5.3.7 把无效的数据解释为有效的数据\r\n5.3.8 允许不确定的行为\r\n5.4 资源限制(Resource-Limit)错误\r\n5.4.1 堆栈溢出\r\n5.4.2 用完空闲存储空间\r\n5.4.3 文件系统限制\r\n5.5 异常安全性\r\n5.5.1 不一致的状态\r\n5.5.2 资源泄漏\r\n5.6 总结\r\n5.7 练习\r\n5.8 参考文献和相关资料\r\n\r\n第6章 冲突\r\n\r\n6.1 全局名称\r\n6.1.1 翻译单元\r\n6.1.2 类的定义\r\n6.1.3 函数和数据的定义\r\n6.1.4 程序库的蕴涵意义\r\n6.1.5 命名约定\r\n6.1.6 namespace(名字空间)结构\r\n6.2 宏名称\r\n6.2.1 宏名称冲突\r\n6.2.2 去掉宏\r\n6.2.3 宏的命名约定\r\n6.3 环境名称\r\n6.4 Unclean程序库\r\n6.5 Good-Citizen程序库\r\n6.6 总结\r\n6.7 练习\r\n6.8 参考文献和相关资料\r\n\r\n第7章 兼容性\r\n\r\n7.1 向后和向前兼容性\r\n7.2 兼容性的形式\r\n7.3 理论源代码兼容性\r\n7.4 实际源代码兼容性\r\n7.5 链接兼容性\r\n7.6 运行兼容性\r\n7.7 进程兼容性\r\n7.8 文档化不兼容性\r\n7.9 非文档化特性\r\n7.1 总结\r\n7.11 练习\r\n7.12 参考文献和相关资料\r\n\r\n第8章 继承体系\r\n\r\n8.1 根数目、深度和扇出数\r\n8.2 体系类型\r\n8.2.1 直接体系\r\n8.2.2 接口体系\r\n8.2.3 对象工厂(Object Factory)\r\n8.2.4 句柄体系\r\n8.3 模板还是继承\r\n8.3.1 指针操纵\r\n8.3.2 派生要求\r\n8.3.3 实现不需要的函数\r\n8.4 总结\r\n8.5 练习\r\n8.6 参考文献和相关资料\r\n\r\n第9章 移植性\r\n\r\n9.1 有编写可移植代码的必要吗\r\n9.1.1 可移植性的优缺点\r\n9.1.2 目标代码和创建过程的可移植性\r\n9.2 不断发展的语言定义\r\n9.2.1 冲突\r\n9.2.2 实现的完整性\r\n9.3 不确定的行为\r\n9.3.1 排列方式和补全(padding)\r\n9.3.2 地址操纵\r\n9.4 合法但不可移植的代码\r\n9.4.1 实现性定义的行为\r\n9.4.2 未经指定的行为\r\n9.5 实现依赖性\r\n9.6 可移植的数据文件\r\n9.7 模板实例化\r\n9.7.1 自动的实例化器\r\n9.7.2 人工实例化\r\n9.8 运行期程序库\r\n9.9 其他移植性问题\r\n9.1 总结\r\n9.11 练习\r\n9.12 参考文献和相关资料\r\n\r\n第10章 使用其他程序库\r\n\r\n10.1 为何要重用其他程序库\r\n10.2 使用其他程序库的缺点\r\n10.2.1 获得可重用程序库\r\n10.2.2 效率\r\n10.2.3 冲突\r\n10.2.4 版本同步\r\n10.3 自含式(Self-Contained)程序库\r\n10.3.1 实现困难\r\n10.3.2 使用困难\r\n10.3.3 效率\r\n10.3.4 隔离\r\n10.4 总结\r\n10.5 练习\r\n\r\n第11章 文档编制\r\n\r\n11.1 文档编制和重用性\r\n11.2 设计文档\r\n11.3 使用指南\r\n11.3.1 对读者的背景知识了如指掌\r\n11.3.2 用抽象的观点来编写\r\n11.3.3 先解释普通用法\r\n11.3.4 一次只解释一个事物\r\n11.3.5 解释用法,不解释设计思路\r\n11.3.6 简单清楚地编写\r\n11.3.7 准确地使用语言\r\n11.3.8 使用普遍接受的术语\r\n11.3.9 深刻理解重载的术语\r\n11.3.10 给出合法的、无错误的代码\r\n11.3.11 保持简短的代码段\r\n11.3.12 避免使用太大的函数\r\n11.3.13 提供在线实例\r\n11.4 参考手册\r\n11.4.1 抽象化\r\n11.4.2 语法接口\r\n11.4.3 函数语义\r\n11.4.4 模板参数约束\r\n11.5 总结\r\n11.6 练习\r\n11.7 参考文献和相关资料\r\n\r\n第12章 其他话题\r\n\r\n12.1 静态初始化问题\r\n12.1.1 构造和析构的时刻\r\n12.1.2 程序库的蕴含意义\r\n12.1.3 初始化函数\r\n12.1.4 初始化检查\r\n12.1.5 初始化对象\r\n12.1.6 双构造\r\n12.2 局部化开销原则\r\n12.2.1 局部化开销和C++\r\n12.2.2 局部化开销和程序库\r\n12.3 内生类和外生类\r\n12.4 迭代器\r\n12.5 类耦合\r\n12.6 推迟决定\r\n12.7 总结\r\n12.8 练习\r\n12.9 参考文献和相关资料\r\n\r\n中英文术语对照表 \r\n参考文献
这本书的主要目的在于:展示如何以C++编程语言编写可重用代码——就是说, 根据不同的需要, 在不经过修改, 或者经过很少修改的前提下, 可重用代码可以很容易地应用到5个. 50个甚至500个程序当中, 而且这些程序往往是不同程序员编写的, 可能运行在不同的系统上. 在整个阐述的过程中, 我们的目的并不在于争论是否所有的代码都是可重用的, 也不在于说明可重用代码能够解决所有的程序问题. 显然, 不论是对程序员而言, 还是对可重用代码本身而言, 提高代码的重用性都是需要代价的, 通常只有当我们有理由相信所给代码在将来有可能会被重用时, 我们才会付出这些重用的代价. 因此, 本书的目的在于详细分析重用性的这些代价, 于是当你面对是否编写可重用代码的选择时, 可以从容地做出明智的决定.
关于本书
本书主要面向的读者是:那些希望从书中包含的许多深层C++编程见解中受益的读者, 或者是那些需要或希望学习如何编写可重用代码的读者, 在论述过程中, 我们假设读者已经知道如何编写正确的C++代码.
C++语言至今还没有经过标准化(译注:本书写作于1995年, C++于1997年标准化), 任意两个不同的编译器实现支持的语言几乎都是不同(稍微不同或者相差很大)的. 当我编写这本书的时候, 并没有一个编译器实现可以完全支持 ANSI/ISO C++标准中最终定义的整个语言特性, 而且, 就算对同类型的编译器而言, 前后版本实现的语言特性也不尽相同. 于是, 编写一本对所有编译器都适用的书是很困难的, 或者是不可能的. 因此, 当我们讨论或使用一些不能被主流C++编译器所实现的特性时, 我们将会另加说明.
当声明本书中的代码例子被认为合法时, 我们所指的合法性是以1994年9月份ANSI/ISOC++的工作文件(有时候也称为“标准草案”)[ANS94]为依据的. 而且, 在我们的代码例子里, 我们将尽量避免使用那些我们认为在最终 ANSI/ISO C++标准公布之前, 很有可能会被删除或者进行重大修改的语言特性.
要跟踪C++语言的演化, 有几个资源是可以利用的. Internet讨论组comp.lang. c和comp. std. c++主要致力于C++的讨论. 互联网中还有一个关于C++的主页:
http://info. desy. de/user/projects/C++. html而由纽约SIGS出版社定期发行的C++ Roport, 也经常会刊登许多C++程序员感兴趣的文章. 至于学习C++的书籍, Lippman的[Lip9l]和Stroustrup的[Sedl]是最佳的学习教材.
任何关于重用性的完整讨论都会涉及到软件开发的一些其他主题, 如接口的设计. 实现的效率. 可移植性. 冲突等等. 在书中的论述中, 我们假设读者对软件开发已经有所了解, 因此, 我们只注重于那些应用于可重用代码的主题, 而不是那些应用于一般软件开发的主题.
“每本教材都会撒谎”, 这是我们老师非常喜欢的一句谚语. 在这里, 她实际要表达的意思是:每本教材为了教学的简化性和明确性, 都必须简化书中的内容, 从而都会歪曲所要表达的信息. 显然, 即使在需要简化的前提下, 我们还是应该用抓住众多细节间的本质信息的方法来表述每个主题, 这本书也不例外. 于是, 对于某些主题, 即使有些读者己经对这个主题的方方面面都有所了解, 我们还是希望他们能够和我们一起共同探讨该主题的内容. 而且, 对于一些主题内容的省略, 我们也希望能够得到这些读者的谅解.
选择与建议
设计和编写可重用代码将会涉及到许多选择(即在多种实现方案中选定某种方案), 而某些选择的决定又是左右为难的. 通常, 对于这些选择, 并没有完全确定或者正确的答案——无论你做出哪个选择, 每个选择都是有代价的. 我们将讨论各种不同选择方案的优点和缺点, 从而让将来可重用代码的编写者可以从容地做出明智的决定.
有时, 对于某个给定的决定, 我们会认为某种风格或者方法要优于其他所有的风格或者方法. 于是, 当遇到这种情况时, 我们会明确地建议采用这种风格或者方法, 并给出支持这种风格或者方法的详细理由. 然而, 在大多数情况下, 只有C++程序库的设计者, 才能决定哪种方法对程序库用户而言是最好的.
这本书的一个目的在于:详细并且清楚地给出编写可重用代码的大多数重要选择. 如果我们忽略了某些重要的选择, 那么我们愿闻其详.
书中大多数关于选择的讨论都会提到效率. 虽然我们并不认为效率是所有可重用C++代码最重要的设计目标, 因为对于许多程序库设计而言, 譬如扩展性. 灵活性等其他特性将会比效率更加重要:但我们在文章中又处处提到效率, 这是因为效率将会和可重用C++代码的其他每个可取的特性都相互制约. 既然对效率已经花费了这么多笔墨, 我们也希望可以给出所有和效率相互制约的因素.
代码例子
我们希望这本书的价值在于:与刚开始读这本书之前相比, 当你读完这本书的时候, 你将知道更多关于如何编写可重用代码的知识. 因此, 我们使用了大量的例子, 而且许多例子的代码是来自于现实中已经存在的代码, 因为, 那些不是取材于真实代码的例子, 不足以阐明实际编程中会出现的问题, 也不足以说明实际编程中使用的各种技术. 在这里, 我们只是为了阐明重用性的目的而利用这些代码.
为了节省篇幅, 成员函数的主体通常都是在类的声明中给出, 即使在重用性的代码设计中, 这些函数也不会被实现为内联函数. 例如, 读者不应该从下面的类定义语句中得出:函数f是内联函数(就是说实际上f不是内联函数)的结论.
class Z{
void f(){
//...
}
},
(关于如何决定某个函数是否应该实现为内联函数, 我们将在4. 4. 回小节讨论这个问题)
当提到模板成员函数的时候, 在不致引起混淆的前提下, 我们通常都会省略模板参数.
例如, 我们将把下面代码中的成员函数f:
template<class T>
class X{
void f(),
},
表示为x::f, 而不是X<T>::f.
通常, 现实程序库例子中给出的类大多是模板, 而在书中, 为了能够使阅读更加容易, 并且有利于更加清楚地表述每个主题, 我们就进行了简化. 相似地, 现实程序库中的嵌套类也并不都如嵌套语法所定义的那样, 但是, 当我们在书中给出一个嵌套类的时候, 我们将不使用前置声明语法. 例如, 我们将如下编写嵌套类:
class X{
class Y{
//...
},
//...
},
而不是如下编写嵌套类:
class X{
class Y,
//...
},
CldSS X::Y {
//...
},
虽然前置声明语法能够提高可重用代码的可读性, 但是许多C+十编译器都不能实现这种语法.
现今, 任何我们可用的编译器, 都还没有实现新的强制转型(cast)语法. 这个语法是一个很新的C++特性(针对于编写这本书的时候), 我们也还不具备应用这个语法的充分经验, 因此, 我们在书中没有使用这个语法.
术语
在本文中, 我们将使用程序库概念来表示可重用代码的集合. 我们也经常互换地使用程序库和可重用代码这两个概念. 另外, 当我们说“编写”代码段的时候, 我们的意思是“设计和实现”这段代码.
我们将使用编译程序来指代任何程序或者任何把源代码翻译成可执行代码的程序集合. 翻译将包括:预处理C+十使之成为另一种语言, 然后把这种语言编译或者汇编成可执行代码.
为了行文简洁, 我们在文章中使用的基类, 不仅用来表述其他类的基类, 而且还表述这个类可让其他类派生自该类.
C++操作符
ostream&operator<<(ostream&o, const T& t),
istream& operator>>(istream&i, T&t),
(对于某个给定的类型T)提供了T类型的流插入和流输出操作符. 为了和通常的使用习惯保持一致, 我们把它们分别叫做T的输入操作符和输出操作符.
关于模板和模板实例化的术语, C+十社区仍然有许多不一致的意见. 不同的人可能会使用术语模板类. 类模板. 实例化. 实例. 特化等中的一个, 来描述不同(或者相似)的事物. 这些术语准确而且标准的定义最后将由ANSI/ISO C+十委员会来决定. 下面说明了我们在此书中如何使用这些概念:
类模板:形如 template<...>X的模板, 其中X为类的定义.
函数模板:形如template<...>f的模板, 其中f为函数的定义.
类模板特化:当类模板的参数被实际参数替代时, 而得到的类.
函数模板特化:当函数模板的参数被实际参数替代时, 而得到的函数.
实例化:产生特化的过程.
模板类:我们不使用这个术语
模板函数:我们不使用这个术语.
例如, 考虑下面的类模板:
template<class T>
class X{
//...
},
类X<int>和类X就是这个模板的两个特化. 另外, 特化通常是隐式定义的, 如:
template<class T>class X{/*...*/},
X<iflt> X, //X<i<t>就是隐式定义的.
偶尔, 某些C+十程序员也会显式定义特化, 例如:
template<class T>class x{/*...*/},
Class X<int>{/*...*/}, //X<int>在这里显式定义.
X<int>x,
而许多人把特化这个术语仅仅看成是显式特化, 这是不正确的. 虽然我们定义的这些术语都是非常相似的, 但我们相信, ANSI/ISO很快将会给出这些术语很好的定义.
通常, 在不会引起混淆的前提下, 我们会使用“类”来代替“类模板”, “函数”来代替“函数模板”.