又看完了一本书,101条有些看的比较仔细,有些浏览而过,有些看了不知所云,有些看了有所感触。是一本很好的书,把书中最后的摘要汇总摘抄了一遍(有几条摘录了正文中的一些条目)。
电子书分享在此:《C++编程规范--101条规则、准则与最佳实践》
组织和策略问题
item 0:不要拘泥于小节(了解哪些东西不应该标准化)只规定需要规定的事情:不要强制施加个人喜好或者过时的做法。
item 1:在高警告级别干净利落地进行编译
高度重视警告:使用编译器的最高警告级别。应该要求构建是十分干净利落的(没有警告)。理解所有的警告。通过修改代码而不是降低警告级别来排除警告。
item 2:使用自动构建系统
一次按键就解决问题:使用完全自动化(单操作)的构建系统,无需用户干预即可构建整个项目。
item 3:使用版本控制系统
好记性不如烂笔头:请使用版本控制系统。永远不要让文件长时间地登出。在新的单元测试通过之后 ,应该频繁登入。确保登入的代码不会影响构建成功。
item 4:在代码审查上投入
审查代码:更多的关注有助于提高质量。亮出自己的代码,阅读别人的代码。互相学习,彼此都会受益。
设计风格
item 5:一个实体应该只有一个紧凑的职责
一次只解决一个问题:只给一个实体(变量、类、函数、名称空间、模块和库)赋予一个定义良好的职责。随着实体变大,其职责范围自然也会扩大,但是职责不应该发散。
item 6:正确、简答和清晰第一
软件简单为美(keep it simple software, KISS):正确优于速度。简单优于复杂。清晰优于机巧,安全优于不安全。
item 7:编程中应知道何时何如何考虑可伸缩性
小心数据的爆炸性增长:不要进行不成熟的优化,但是要密切关注渐进复杂性。处理用户数据的算法对所处理的数据量耗费的时间应该是可预测的,最好不差于线性关系。
如果能够证明优化必要而且非常重要,尤其在数据量逐渐增长的情况下,那么应该集中精力改善算法的O(N)复杂性,而不是进行小型的优化,比如节省一个多余的加法运算。
item 8:不要进行不成熟的优化
拉丁谚语云:快马无需鞭策:不成熟的优化的诱惑非常大,而它的无效性也同样严重。优化的第一原则就是:不要优化。优化的第二原则(适用于专家)是还是不要优化。
再三测试而后优化。
item 9:不要进行不成熟的劣化
放松自己,轻松编程:在所有其他事情特别是代码复杂性和可读性都相同的情况下,一些高校的设计模式和编程惯用法会从你的指尖自然流出,而且不会比悲剧的替代方案更
难写。这并不是不成熟的优化,而是避免不必要的劣化。
item 10:尽量减少全局和共享数据
共享会导致冲突:避免共享数据,尤其是全局数据。共享数据会增加耦合度,从而减低可维护性,通常还会影响性能。
item 11:隐藏信息
不要泄密:不要公开提供抽象的实体和内部信息。
item 12:懂得何时何如何进行并发性编程
安线全程地(故意安排,即线程安全地):如果应用程序使用了多个线程或者进程,应该知道如何尽量减少共享对象,以及如何安全地共享必须共享的对象。
item 13:确保资源为对象所拥有。使用显式地RAII和智能指针
利器在手, 不要再徒手为之:C++的“资源获取即初始化”(resource acquisition is initialization, RAII)惯用法是正确处理资源的利器。RAII是编译器能够提供强大且自动的保证。
这在其他语言中可能是需要脆弱的手工编写的惯用法才能实现的。分配原始资源的时候,应该立即将其传递给属主对象。永远不要在一条语句中分配一个以上的资源。
item 14:你要编译时和链接时错误,不要运行时错误
能够在编译时做的事情,就不要推迟到运行时:编写代码时,应该在编译期间使用编译器检查不变式,而不应该在运行时再进行检查。运行时检查取决于控制流和数据的具体情况,这意味着很难
知道检查是否彻底。相比而言,编译时检查与控制流和数据无关,一般情况下能够获得更高的可信度。
item 15:积极使用const
const是我们的朋友:不变的值更易于理解、跟踪和分析,所以应该尽可能的使用常量代替变量,定义值的时候,应该把const作为默认的选项:常量很安全,在编译时会对其
进行检查,而且它与C++的类型系统已浑然一体。不要强制转换const的类型,除非要调用常量不正确的函数。
item 16:避免使用宏
实不相瞒:宏是C和C++语言的抽象设施中最生硬的工具,它是披着函数外衣的饥饿的狼,很难驯服,它会我行我素地游走于各处。要避免使用宏。
item 17:避免使用“魔数”
程序设计并非模式,所以不要故弄玄虚:要避免在代码中使用诸如42和3.14159这样的文字常量。它本身没有提供任何说明,并且因为增加了难于检测的重复而使维护更加复杂。
可以用符号名称和表达式代替它们,比如width*aspectRatio。
item 18:尽可能局部地声明变量
避免作用域膨胀,对于需求如此,对于变量也是如此。变量将引入状态,而我们应该尽可能少地处理状态,变量的生存期也是越短越好。这是item 10的一个特例,但是值得单独阐述。
item 19:总是初始化变量
一切从白纸开始:未初始化的变量是C和C++程序中错误的常见来源。养成在使用内存之前先清除的习惯,可以避免这种错误,在定义变量的时候就将其初始化。
item 20:避免函数过长,避免嵌套过深
短胜于长,平优于深:过长的函数和嵌套过深的代码块的出现,经常是因为没能赋予一个函数以一个紧凑的职责所致,这两种情况通常 都能够通过更好的重构予以解决。
item 21:避免跨编译单元的初始化依赖
保持初始化顺序:不同编译单元中的名字空间级对象绝不应该在初始化上相互依赖,因为其初始化顺序是未定义的,这样做会惹出很多麻烦,轻则在项目中稍作修改就会引发奇怪的崩溃,
重则出现严重的不可移植问题——即使是同一个编译器的新版本也不可以。
item 22:尽量减少定义性依赖,避免循环依赖
不要过分依赖:如果用前向声明能够实现,那么就不要包含定义。
不要互相依赖:循环依赖是指两个模块直接或者间接地互相依赖。所谓模块就是一个紧凑的发布单元,互相依赖的多个模块并不是真正的独立模块,而是仅仅胶着在一起的一个更大的模块,
一个更大的发布单元。因此,循环依赖有助于模块性,是大型项目的祸根。避免循环依赖。
item 23:头文件应该自给自足
各司其职:应该确保所编写的每个头文件都能够独自进行编译,为此需要包含其内容所依赖的所有头文件。
item 24:总是编写内部#include保护符,绝不要编写外部#include保护符
为头文件添加保护:在所有头文件中使用带有唯一名称的包含保护符防治无意的多次包含。
函数与操作符
Item 25:正确地选择通过值、(智能)指针或者引用传递参数
正确选择参数:分清输入参数、输出参数和输入/输出参数,分清值参数和引用参数。正确地传递参数。
Item 26:保持重载操作符的自然语义
稃序员讨厌意外情况:只在有充分理由时才重载操作符.而且应该保持其自然语义:如果做到这一点很困难,那么你可能已经误用了操作符重载。
Item 27:优先使用算术操作符和赋值操作符的标准形式
如果要定义a+b,也应该定义a+=b: 在定义一元算术操作符时,也应该提供操作符的赋值形式,并且应该尽置减少重复,提高效率。
Item 28:优先使用++和--的标准形式。优先调用前缀形式
如果定义++c,也要定义c++:递增和递减操作符很麻烦,因为它们都有前缀和后缀形式,而两种形式语义又略有不同。定义operator++和operator--时,应该模仿它们对应的内置操作符,
如果不需要原值,应该优先调用前缀版本。
Item 29:考虚重载以避免隐含类型转换
如无必要勿增对象(奥卡姆剃刀原埋):隐式类型转换提供了语法上的便利。但是如果创建临时对象的工作并不必要而且适于优化,
那么可以提供签名与常见参数类型精确匹配的重载函数,而且不会导致转换。
Item 30:避免重载&&,||或,(逗号)
明智就是知道何时应该适可而止:内置的&&、||和,(逗号)得到了编译器的特殊照顾。如果重载它们,它们就会变成普通函数,具有完全不同的语义,这肯定会引入微妙的错误和缺陷。
不要轻率地重载这些操作符。
Item 31:不要编写依赖于函数参数求值顺序的代码
保持(求值)顺序:函数参数的求值顺序是不确定的,因此不要依赖具体的顺序。
Item 32:弄清所要编写的是哪种类
了解自我有很多种不同的类。弄清楚要编写的是哪一种。
1、值类:ex:std::pair,std::vector模仿的是内置类型,一个值类应该:
*有一个公共析构函数,复制构造函数和带有值语义的赋值;
*没有虚拟函数(包括析构函数);
*是用作具体类,而不是基类;
*总是在栈中实例化,或者作为另一个类直接包含的成员实例化;
2、基类:类层次结构的构成要素。一个基类应该:
*有一个公用而且虚拟或者保护而且非虚拟的析构函数和一个非公用复制构造函数和赋值操作符;
*通过虚拟函数实现接口;
*只是动态地在堆中实例化为具体派生类对象,并通过一个(智能)指针来使用;
3、不严格地来说,traits类携带有关类型信息的模板。一个traits类应该:
*只包含typedef和静态函数。没有可修改的状态或者虚拟函数;
*通常不实例化(其构造一般是被禁止的);
4、策略类(通常是模板)是可插拔行为的片段。一个策略类应该:
*可能有也可能没有状态或者虚拟函数。
*通常不独立实例化,只作为基类或者成员。
5、异常类提供了不寻常的值与引用语义的混合:它们通过值抛出,但应该通过引用捕获。一个异常类应该:
*有一个公用析构函数和不会失败的构造函数(特别是一个不会失败的复制构造函数,从异常的复制构造函数抛出将是程序中止)。
*有虚拟函数,经常实现克隆和访问。
*从std::exception虚拟派生更好。
6、附属类一般支持某些具体的惯用法(如RAII),正确使用是很容易的,想误用反倒难了。
Item 33:用小类代替巨类
分而治之:小类更易于编写,更易于保证正确、测试和使用。小类更有可能适用于各种不同情况。应该用这种小类体现简单概念,不要用大杂烩式的类,它们要实现的概念既多又复杂。
Item 34:用组合代替继承
避免继承带来的重负;继承是C++中第二紧密的耦合关系,仅次于友元关系。紧密的耦合是一种不良现象,应该尽量避免,因此,应该用组合代替继承,除非知道后者确实对设计有好处。
Item 35:避免从并非要设计成基类的类中继承
有些人并不想生孩子,本意是要独立使用的类所遵守的设计蓝图与基类不同。将独立类用作基类是一种严重的设计错误,应该避免.要添加行为,应该添加非成员函数而不是成员函数。
要添加状态,应该使用组合而不是继承。要避免从具体的基类中继承。
Item 36:优先提供抽象接口
偏爱抽象艺术吧:抽象接口有助于我们集中精力保证抽象的正确性,不至于受到实现或者状态管理细节的干扰。优先采用实现了(建模抽象概念的)抽象接口的设计层次结构。
依赖倒置原理(Dependency Inversion Principle,DIP),DIP描述如下:
*高层模块不应该依赖于底层模块。相反,两者都应该依赖抽象。
*抽象不应该依赖于细节。相反,细节应该依赖抽象。
遵守DIP意味着层次结构应该以抽象类而不是具体类为根。抽象基类必须负责定义功能而不是实现功能。
换言之:策略应该上推,而实现应该下放。
Item 37:公用继承即可替换性。继承,不是为了重用,而是为了被重用
知其然:公用继承能够使基类的指针或者引用实际指向某个派生类的对象.既不会破坏代码的正确性,也不需要改变已有代码。
还要知其所以然:不要通过公用继承重用(基类中的已有)代码,公用继承是为了被(已经多态地使用了基对象的已有代码)重用的。
Item 38:实施安全的改写
负责任地进行改写:改写一个虚拟函数时,应该保持可替换性;说得更具休一些,就是要保持基类中函数的前后条件。不要改变虚拟函数的默认参数。应该显式地将改变虚拟函数的默认参数。
应该显式地将改写函数重新声明为virtual。 谨防在虛拟类中隐藏重载函数。
Item 39:考虑将虚拟函数声明为非公用的,将公用函数声明为非虛拟的
在基类中进行修改代价高昂(尤其是库中和框架中的基类):请将公用函数设为非虚拟的。应该将虚拟函数设为私有的,或者如果派生类需要调用基类版本,则设为保护的。
(请注意,此建议不适用于析构函数;见item50)
Item 40:要避免提供隐式转換
并非所有的变化都是进步:隐式转换所带来的影响经常是弊大于利。在为自定义类型提供隐式转换之前,请三思而行,应该依赖的是显式转换(explicit构造函数和命名转换函数)。
Item 41:将数据成员设为私有的,无行为的聚集(C语言形式的struct)除外
它们不关调用者的事:将数据成员设为私有的。简单的C语言形式的struct类型只是将一组值聚集在了一起,并不封装或者提供行为.只有在这种struct类型中才可以将所有数椐成员都设成公用的。
要避免将公用数据和非公数据混合在一起,因为这几乎总是设计混乱的标志。
Item 42:不要公开内部数据
不要过于自动自发:避免返回类所管理的内部数据的句柄,这样类的客户就不会不受抑制地修改对象自己拥有的状态。
Item 43:明智地使用Pimpl
抑制语言的分离欲望:C++将私有成员指定为不可访问的,但并没有指定为不可见的。虽然这样 自有其好处,但是可以考虑通过Pimpl惯用法使私有成员真正不可见,
从而实现编译器防火墙,并提高信息隐藏度。
C++将私有成员指定为不可访问的,但并没有指定为不可见的。虽然这样自有其好处,但是可以考虑通过Pimpl惯用法使私有成员真正不可见,从而实现编译器防火墙,并提高信息隐藏度。
Pimpl惯用法:将私有部分隐藏在一个不透明的指针(即指向已经声明但是尚未定义的类的指针,最好是选择合适的智能指针)后面。
应该用Pimpl来存储所有的私有成员,包括成员数据和私有成员函数。这使我们能够随意改变类的私有实现细节,而不用重新编译调用代码---独立和自由正是这个惯用法的标记性特点。
Item 44:优先编写非成员非友元函数
要避免交成员费:尽可能将函数指定为非成员非友元函数。
Item 45:总是一起提供new和delete
它们是一揽子交易:毎个类专门的重载void* operator new(parms)都必须与对应的重载void operator delete(void* parms)相随相伴,其中parms是额外参数类型的一个列表(第一个总是 std::size_t)。
数组形式的new[]和delete|]也同样如此。
Item 46:如果提供类专门的new,应该提供所有标准形式(普通、就地和不抛出)
不要隐藏好的new;如果类定义了operator new的重载,则应该提供operator new所有三种形式——普通(plain),就地(in-place)和不抛出(nothrow)的重载。不然,类的用户就无法看到和使用它们。
Item 47:以同样的顺序定义和初始化成员变量
与编译器一致:成员变量初始化的顺序要与类定义中声明的顺序始终保持一致:不用考虑构造函数初始化列表中编写的顺序。要确保构造函数代码不会导致混淆地指定不同的顺序。
Item 48:在构造函数中用初始化代替赋值
设置一次,到处使用:在构造函数屮,使用初始化代替赋值来设置成员变量,能够防止发生不必要的运行时操作,而输入代码的工作量则保持不变。
Item 49:避免在构造函数和析构函数中调用虚拟函数
虚拟函数仅仅“几乎”总是表现得虚拟:在构造函数和析构函数中,它们并不虚拟,更糟糕的是, 从构造函数或析构函数直接或者间接调用未实观的纯虚拟函数,会导致未定义的行为。
如果设计方案希望从基类构造函数或者析构函数虚拟分派到派生类,那么需要来用其他技术,比如后构造凼数 (post-constructor)。
Item 50:将基类析构函数设为公用且虚拟的,或者保护且非虚拟的
删除,还是不删除,这是个问题:如采允许通过指向基类Base的指针执行删除操作,则Base 的析构函数必须是公用且虚拟的。否则,就应该是保护且非虚拟的。
Item 51:析构函数、释放和交换绝对不能失败
它们的一切尝试都必须成功:决不允许析构函数、资源释放(deallocation)函数(如operator delete) 或者交换函数报错。说得更具体-些、就是绝对不允许将那些析构闲数可能会抛出异常的类型用于C++标准库。
Item 52:一致地进行复制和销毁
既要创建,也要清除:如果定义了复制构造函数、复制赋值操作符或者析构函数中的任何一个,那么可能也需要定义另一个或者另外两个。
Item 53:显式地启用或者禁止复制
清醒地进行复制:在下述二种行为之间谨慎选样——使用编译器生成的复制构造函数和赋值操作符:编写自己的版本:如果不应允许复制的话,显式地禁用前两者。
Item 54:避免切片。在基类中考虑用克隆代替复制
切片面包很好;切片对象则不然:对象切片是自动的、不可见的,而且可能会使漂亮的多态设计嘎然而止。在基类中,如果客户需要进行多态(完整的、深度的)复制的话,
那么请考虑禁止复制构造函数和复制赋值操作符.而改为提供虚拟的clone成员函数。
Item 55:使用赋值的标准形式
赋值,你的任务:在实现operator=时,应该使用标准形式——具有特定签名的非虚拟形式。
Item 56:只要可行,就提供不会失畋的swap (而且要正确地提供)
swap既可无关痛痒,又能举足轻重:应该考虑提供一个swap函数,高效且绝对无误地交换两个对象,这样的函数便于实现许多惯用法,从流畅地将对象四处移动以轻易地实现赋值,
到提供一个有保证的、能够提供强大防错调用代码的提交函数。
Item 57:将类型及非成员函数接口置于同一名字空间中
非成员也是函数:如果要将非成员函数(特别是操作符和辅助函数)设计成类X的接口的一部分, 那么就必须在与X相同的名字空间中定义它们,以便准确调用。
Item 58:应该将类型和函数分别置于不同的名字空间中,除非有意想让它们一起工作
协助防止名字査找问题;通过将类型(以及与其直接相关的非成员函数,见第57条) 置于自己单独的名字空间中,可以使类型与无意的ADL (参数依赖査找,也称Koenig査找)隔离开来,
促进有意的ADL。要避免将类型和模板化函数或者操作符放在相同的名字空间中。
Item 59:不要在头文件中或者#include之前编写名字空间using
名字空间using是为了使我们更方便,而不是让我们用来叨扰别人的,绝对不要编写using声明或者在#include之前编写using指令。
推论:在头文件中,不要编写名字空间级的using指令或者using声明,相反应该显式地用名字空间限定所有的名字。简而言之:可以而且应该在实现文件中的#include指令之后自由地
使用名字空间级的using声明和指令,而且会感觉良好。
(第二条规则是从第一条直接得出的,因为头文件无法知道以后其他头文件会出现什么样的#include。)
Item 60:要避免在不同的模块中分配和释放内存
物归原位:在一个模块中分配内存,而在另一个模块中释放它,会在这两个模块之间产生微妙的远距离依赖,使程序变得脆弱,必须用相同版本的编译器、
同样的标志(比较著名的比如用debug 还是NDEBUG) 和相同的标准库实现对它扪进行编译,实践中,在释放内存时,用来分配内存的模块最好仍在内存中。
Item 61:不要在头文件中定义具有链接的实体
重复会导致膨胀:具有链接的实休(entity with linkage),包括名字空间级的变量或函数,都需要分配内存。在头文件中定义这样的实体将异致连接时错误或者内存的浪费。
请将所有具有链接的实体放入实现文件。
Item 62:不要允许异常跨越模块边界传播
不要向邻家的花园抛掷石头:C++异常处埋没有普遍通用的二进制标准。不要在两段代码之间传播异常,除非能够控制用来构建两段代码的编译器和编译选项:
否则模块可能无法支持可兼容地实现异常传播。这通常可以一言以蔽之:不要允许异常跨越模块或子系统边界传播。
Item 63:在模块的接口中使用具有良好可移植性的类型
生在(模块的)边缘.必须格外小心:不要让类型出现在模块的外部接口中.除非能够确保所有的客户代码都能够正确地理解该类型。应该使用客户代码能够理解的最高层抽象。
Item 64:理智地结合静态多态性和动态多态性
1加1可远远不止是2:静态多态性和动态多态性是相辅相成的。理解它们的优缺点,善用它们的长处,结合两者以获得两方面的优势。
Item 65:有意地进行显式自定义
有意胜过无意,显式强似隐式:在编写模板时,应该有意地正确地、提供自定义点,并清晰地记入文档。在使用模板时,应该了解模板想要你如何进行自定义以将其用于你的类型,并且正确地自定义。
Item 66:不要特化函数模板
只有在能够正确实施的时候,特化才能起到好作用:在扩展其他人的函数模板(包括std::swap)时,要避免尝试编写特化代码:相反,要编写函数模板的重载,
将其放在重载所用的类型的名字空间中,编写自己的函数模板时,要避免鼓励其他人直接特化函数椹板本身。
Item 67:不要无意地编写不通用的代码
依赖抽象而非细节:使用最通用、最抽象的方法来实现一个功能。
Item 68:广泛地使用断言记录内部假设和不变式
使用断言吧!广泛地使用assert或者等价物记录模块内部(也就是说,调用代码和被调用代码由同一个人或者小组维护)的各种假设,这些假设是必须成立的,
否则就说明存在编程错误(例如:函数的调用代码检査到函数的后条件不成立)。当然,要确保断言不会产生任何副作用。
Item 69:建立合理的错误处埋策略,并严格遵守
应该在设计早期开发实际、一致、合理的错误处埋策略,并予以严格遵守。许许多多的项目对这一点的考虑(或者错误佔估计)都相当草率,应该对此有意识地规定,并认真应用。
策略必须包含以下内容:
·鉴别:哪些情况属于错误。
·严重程度:毎个错误的严重性或紧急性。
·检查:哪些代码负责检查错误。
·传递:用什么机制在模块中报告和传递错误通知。
·处理:哪些代码负责处理错误。
·报告:怎样将错误记入日志,或通知用户。
只在模块边界处改变错误处理机制。
Item 70:区别错误与非错误
违反约定就是错误:函数是一个工作单元。因此,失败应该视为错误,或根据其对函数的影响而定,在函数f中,当且仅当失败违反了f的一个前条件,
或者阻碍了f满足其调用代码的任何前条件、实现f自己的任何后条件或者重新建立f有责任维持的不变式时,失败才是个错误。
这里我扪特別排除了内部的程序设计错设(即调用代码和被调用代码都由同一个人或者同一个团队负责,比如位于一个模块中),这种错误一般可以使用断言来解决。
Item 71:设计和编写错误安全代码
承诺:但是不惩罚,在所有函数中,都应该提供最强的安全保证,而且不应惩罚不需要这种保证的调用代码。至少要提供基本保证。
确保出现错误时程序会处于有效状态。这是所谓的基本保证(basic guarantee),要小心会破坏不变式的错误(包括仍是不限于泄漏),它们肯定都是bug。
应该进一步保证最终状态要么是最初状态(如果有错误,则回滚操作),要么是所希望的目标状态(如果没有错误,则提交操作)。这就是所谓的强保证(strong guarantee)。
应该进一步保证操作永远不会失败。虽然这对于大多数函数来说是不可能的,但是对于析构函数和释放函数这样的函数来说则是必须的。这就是所谓的不会失败保证(no-fail guarantee)。
Item 72:优先使用异常报告错误
出现问题时,就使用异常:应该使用异常而不是错误码来报告错误。但不能使用异常时,对于错误以及不是错误的情况,可以使用状态码(比如返回码,errno)来报告异常。
当不可能从错误中恢复或者不需要恢复时,可以使用其他方法,比如正常终止或者非正常终止。
Item 73:通过值抛出,通过引用捕获
学会正确捕获(catch):通过值(而非指针)抛出异常,通过引用(通常是const的引用)捕获异常,这是与异常语义配合最佳的组合,
当重新抛出相同的异常时,应该优先使用throw;避免使用 throw e;
Item 74:正确地报告、处埋和转换错误
什么时候说什么话:在检查出并确认是错误时报告错误。在能够正确处理错误的最近一层处理或者转换毎个错误。
Item 75:避免使用异常规范
对异常规范说不:不要在函数中编写异常规范,除非不得以而为之(因为其他无法修改的代码已经使用了异常规范,见本条例外情况)。
Item 76:默认时使用vector。否则,选择其他合适的容器
使用“正确的容器”才是正道:如果有充分的理由使用某个特定容器类型,那就用好了,因为我 们心中有数:自己做出了正确的选择。
使用vector同样如此:如果没有充分理由,那就编写vector,继续前进,无需停顿,我们同样心中有数:自己做出了正确的选择。
Item 77:用vector和string代替数组
何必用贵重的明代花瓶玩杂耍呢?不要使用C语言风格的数铒,指针运算和内存管理原语操作实现数组抽象,使用vector或者string不仅更轻松,而且还有助于编写更安全、伸缩性更好的软件。
Item 78:使用vector (和string::c_str)与非C++ API交换数据
Vector不会在转换中迷失:vector和string::c_str是与C++ API通信的通道。但是不要将迭代器当作指针。要获取vector<T>::iterator iter所引用的元素地址,应该使用&*iter。
Item 79:在容器中只存储值和智能指针
在容器中存储值对象:容器假设它们所存放的是类似值的类型,包括值类型(直接存放)、智能指针和迭代器。
Item 80:用push_back代替其他扩展序列的方式
尽可能地使用push_back:如果不需要操心插入位置,就应该使用push_back在序列中添加元素。其他方法可能极慢而且不简明。
Item 81:多用范围操作,少用单元素操作
顺风顺水无需桨(据拉丁谚语):在序列容器中添加元素时,应该多用范围操作(例如接受一对迭代器为参数的insert形式),而不要连续调用该操作的单元素形式。
调用范围操作通常更易于编写,也更易于阅读,而且比显式循坏的效率更高。
Item 82:使用公认的惯用法真正地压缩容量,真正地删除元素
使用有效减肥法:要真正地压缩容器的多余容量,应该使用“swap魔术”惯用法。要真正地删除容器中的元素,应该使用erase-remove惯用法。
Item 83:使用带检査的STL实现
安全第一:即使只在其中的一个编译器平台上可用,即使只能在发行前的测试中使用,也仍然要使用带检查的STL实现。
Item 84:用算法调用代替手工编写的循环
明智地使用函数对象:对非常简单的循环而言,手工编写的循环有可能是最简单也是最有效率的解决方案。
但是编写算法调用代替手工编写的循环,可以使表达力更强、维护性更好、更不易出错,而且同样高效。
调用算法时,应该考虑编写自定义的函数对象以封装所需的逻辑。不要将参数绑定器 (parameter-binder)和简单的函数对象凑在一起(例如bind2nd和plus),通常这会降低清晰性。
还可以考虑尝试[Boost]的Lambda库,这个库自动化了函数对象的编编写过程。
Item 85:使用正确的STL查找算法
选择查找方式应“恰到好处——正确的查找方式应该使用STL (虽然比光速慢,但已经非常快了):本条款适用于在一个范围内查找某个特定值,或者查找某个值的位置(如果它处于范围内的话)。
査找无序范围,应使用find/find_if或者count/count_if。査找有序范围,应使用lower_bounds、upper_bound、equal_range或者
(在少数情况下)binary_search(尽管 binary_search有一个通行的名字,但是选择它通常并不正确)。
Item 86:使用正确的STL排序算法
选择排序方式应“恰到好处”:理解每个排序算法的作用,选择能够实现所需而开销最低的算法。
Item 87:使谓词成为纯函数
保持谓词纯洁性:谓词就是返回是或否(返回值通常为bool类型)的函数对象。从数学的意义上来说,如果函数的结果只取决于参数,
则该函数就是一个纯函数(请注意,这里“纯“的用法与纯虚拟函数毫无关系)。
不要让谓词保存或访问对其operator()结果有影响的状态,包括成员状态和全局状态。应该使 operator()成为谓词的const成员函数。
Item 88:算法和比较器的参数应多用函数对象少用函数
对象的适配性比函数好:应该向算法传递函数对象,而非函数。关联容器的比较器必须是函数对象。函数对象的适配性好,而且与直觉相反,它们产生的代码一般比函数要快。
Item 89:正确编写函数对象
成本要低,而且要可适配:将函数对象设计为复制成本很低的值类型。尽可能地让它们从unary_function或 binary_function 继承,从而能够适配。
Item 90:避免使用类型分支,多使用多态
切勿分支:避免通过对象类型分支来定制行为。使用模板和虚函数,让类型自己(而不是调用它们的代码)来决定行为。
Item 91:依赖类型,而非其表示方式
不要企图给对象拍X光片,不要对对象在内存中的准确表示方式做任何假设。相反,应该让类型决定如何在内存中读写其对象。
Item 92:避免使用reinterpret_cast
谎言总是站不住脚(德国和罗马尼亚谚语):不要尝试使用reinterpret_cast强制编译器将某个类型对象的内存表示重新解释成另一种类型的对象。
这违反了维护类型安全性的原则,尤其可怕的是reinterpret_cast甚至不能保证是否能够达到这一目的,也无法保证其他功能。
Item 93:避免对指针使用static_cast
不要对动态对象的指针使用static_cast:安全的替代方法有很多,包括使用dynamic_cast,重构,乃至重新设计。
Item 94:避免强制转换const
莫以恶小而为之:强制转换const有时会导致未定义的行为,即使合法,也是不良编程风格的主要表现。
Item 95:不要使用C风格的强制转换
年纪并不意味着智慧:C语言风格的强制转换根据上下文具有不同(而且经常很危险)的语义,而所有这些都隐藏在相同的语法背后。用C++风格的强制转换代替C风格的强制转换有助于防范意想不到的错误。
Item 96:不要对非POD进行memcpy操作或者memcmp操作
不要企图给对象拍X光片:不要用memcpy或memcmp来复制或比较任何对象,除非有什么对象的布局就是原始内存。
Item 97:不要使用联合重新解释表示方式
偷梁换柱也是一种欺骗:通过在union中写入一个成员而读取另一个的滥用方式可以获得“无需强制转换的强制转换”这比起reinterpret_cast更阴险,也更难预测。
Item 98:不要使用可变长参数(...)
省略会导致崩溃:省略号(...)是来自C语言的危险遗产。要避免使用可变长参数,应改用高级的C++结构和库。
Item 99:不要使用失效对象。不要使用不安全函数
不要使用失效药:失效对象和老的但是不安全的函数会对程序的健康产生极大的破坏。
Item 100:不要多态地处理数组
数组的可调整性很差:多态地处理数组是绝对的类型错误,而且编译器有可能不会做出任何提示。不要掉入这一陷阱。