写点什么

《C++ 编程规范 -101 条规则 准则与最佳实践》读书笔记

作者:老王同学
  • 2023-03-18
    广东
  • 本文字数:5488 字

    阅读完需:约 18 分钟

组织和策略问题


第 0 条 不要拘泥于小节(又名:了解哪些东西不应该标准化)

无需在多个项目或者整个公司范围内强制实施一致的编码格式。


第 1 条 在高警告级别干净利落地进行编译

高度重视警告:使用编译器的最高警告级别。通过修改代码而不是降低警告级别来排除警告。


第 2 条 使用自动构建系统

一键构建。


第 3 条 使用版本控制系统

svn。


第 4 条 在代码审查上投入

做好同行评审。


设计风格


第 5 条 一个实体应该只有一个紧凑的职责

一次只解决一个问题。一个实体或一个模块,只赋予一个良好的职责,不要乱发散。


第 6 条 正确、简单和清晰第一

代码是写给人看的,要简单、清晰、可靠。


第 7 条 编程中应知道何时和如何考虑可伸缩性

关注算法的优化,注意复杂性。


第 8 条 不要进行不成熟的不要进行不成熟的优化,优化应该使代码更清晰易读,易理解,易重构,而不要为了所谓的性能,让代码变得更复杂和更差的可读性。


第 9 条 不要进行不成熟的劣化

不要使用低效的用法,比如++,应倾向于使用前++,而不是会产生临时变量的后++。


第 10 条 尽量减少全局和共享数据


全局和共享数据,会增加耦合度,降低可维护性。


第 11 条 隐藏信息

模块或对象设计,内部实现与外部接口要分离,减少依赖性。


第 12 条 懂得何时和如何进行并发性编程


多线程,与平台相关。小心使用各种多线程技术。


第 13 条 确保资源为对象所拥有。使用显式的 RAII 和智能指针

“资源获取即初始化”,是处理资源获取和释放的 C++惯用法。局部对象的构造函数和析构函数,竟然可以解决资源自动释放的难题,C++真是无时无刻不让人惊叹。


编程风格


第 14 条 宁要编译时和连接时错误,也不要运行时错误

C++属于静态类型语言,应当好好利用其静态类型检查。多依赖编译时检查,而不要过多的依赖运行时检查。


第 15 条 积极使用 const

Const 是我们的好朋友。


第 16 条 避免使用宏

C++中几乎不需要宏:const/enum,inline,template,namespace 等多种机制,分别取代宏的作用。当然头文件中的 #ifndef 那个还是没有好的选择。


第 17 条 避免使用“魔数”

硬编码的数字,应当使用常量代替。


第 18 条 尽可能局部地声明变量

变量用的着的时候才定义,定义马上初始化。


第 19 条 总是初始化变量


第 20 条 避免函数过长,避免嵌套过深


第 21 条 避免跨编译单元的初始化依赖

不同编译单元中的名字空间级对象,其初始化顺序是未定义的。


第 22 条 尽量减少定义性依赖。避免循环依赖

只需要声明的时候,就不要提供定义。


第 23 条 头文件应该自给自足

应该确保每个头文件都能够独立进行编译,需要包含其内容所依赖的所有头文件。


第 24 条 总是编写内部 #include 保护符,决不要编写外部 #include 保护符

#ifndef xxx #define xxx #endif,这个破玩意。


函数与操作符

第 25 条 正确地选择通过值、(智能)指针或者引用传递参数

分清输入/输出参数。


第 26 条 保持重载操作符的自然语义

不要定义和操作符名称不符的重载。


第 27 条 优先使用算术操作符和赋值操作符的标准形式


第 28 条 优先使用++和--的标准形式。优先调用前缀形式

T& T::operator++(); //前缀形式

T& T::operator++(int); //后缀形式


第 29 条 考虑重载以避免隐含类型转换


第 30 条 避免重载 &&、||或 ,(逗号)


第 31 条 不要编写依赖于函数参数求值顺序的代码

不同的编译器处理函数参数求值顺序可能不一样。


类的设计与继承

第 32 条 弄清所要编写的是哪种类

不同种类的类适用于不同用途。值类?基类?traits 类?策略类?异常类?


第 33 条 用小类代替巨类

小类更易于编写,更易于保证正确、测试和使用。小类更有可能适用于各种不同情况。

小的类粒度层次恰到好处,被人使用和重用的可能性也越大,更易于部署。


第 34 条 用组合代替继承

继承是紧密的耦合关系。“组合”就是指在一个类型中嵌入另一个类型的成员变量。用这种方式能够保存和使用对象,还能控制耦合强度。


第 35 条 避免从并非要设计成基类的类中继承

不要再不需要的情况下使用继承。要继承的话,就要设计专门的基类。


第 36 条 优先提供抽象接口

抽象接口是完全由(纯)虚函数构成的抽象类,没有状态(成员数据)。抽象基类必须负责定义功能,而不是实现功能。策略应该上推,而实现应该下放。


第 37 条 公用继承即可替换性。继承,不是为了重用,而是为了被重用

公用继承能够使基类的指针或者引用实际指向某个派生类的对象,既不会破坏代码的正确性,也不需要改变已有代码。

不要通过公用继承重用代码,公用继承是为了被重用的。


第 38 条 实施安全的改写

改写一个虚拟函数时,应该保持可替换性,就是要保持基类中函数的前后条件。不要改变虚拟函数的默认参数。


第 39 条 考虑将虚拟函数声明为非公用的,将公用函数声明为非虚拟的


第 40 条 要避免提供隐式转换

隐式转换会在最意料不到的地方抛出异常;并不总是能与语言的其他元素有效的配合。尽量使用显式转换。


第 41 条 将数据成员设为私有的,无行为的聚集(C 语言形式的 struct)除外

要避免将公用数据和非公用数据混合在一起。拥有公用数据意味着类的部分状态的变化可能是无法控制的、无法预测的、与其他状态异步发生的。


第 42 条 不要公开内部数据

避免返回类所管理的内部数据的句柄,这样类的客户就不会不受控制的修改对象自己拥有的状态。

数据隐藏是一种强大的抽象方式,也是强大的模块化机制。


第 43 条 明智地使用 Pimpl

如果创建“编译器防火墙”将调用代码与类的私有部分完全隔离是明智的,就应该使用 Pimpl 惯用法:将私有部分隐藏在一个不透明的指针(即指向已经声明但是尚未定义的类的指针,最好是选择合适的智能指针)后面。


第 44 条 优先编写非成员非友元函数

非成员非友元函数通过尽量减少依赖提高了封装性:函数体不能依赖于类的非公用成员。


第 45 条 总是一起提供 new 和 delete


第 46 条 如果提供类专门的 new,应该提供所有标准形式(普通、就地和不抛出)


构造、析构与复制


第 47 条 以同样的顺序定义和初始化成员变量

成员变量初始化的顺序要与类定义中声明的顺序始终保持一致;不用考虑构造函数初始化列表中编写的顺序。要确保构造函数代码不会导致混淆地指定不同的顺序。

C++语言之所以采取这样的设计,是因为要确保销毁成员的顺序是唯一的;否则,析构函数将以不同顺序销毁对象,具体顺序取决于构造对象的构造函数。


第 48 条 在构造函数中用初始化代替赋值

在初始化列表中初始化成员变量,代码表达意图更加明确,代码通常还会更小、更快。A():s1_(“hello”), s2_(“world”){}


第 49 条 避免在构造函数和析构函数中调用虚拟函数


第 50 条 将基类析构函数设为公用且虚拟的,或者保护且非虚拟的


第 51 条 析构函数、释放和交换绝对不能失败


第 52 条 一致地进行复制和销毁

如果定义了复制构造函数、复制赋值操作符或者析构函数中的任何一个,那么也需要定义另外两个。


第 53 条 显式地启用或者禁止复制

要清醒的知道自己选择什么样的行为:是使用编译器生成的默认复制构造函数和赋值操作符;还是编写自己的版本;或者如果不允许复制的话,显式地禁用前两种方式。


第 54 条 避免切片。在基类中考虑用克隆代替复制

在基类中,如果客户需要进行多态(完整的、深度的)复制的话,考虑禁止复制构造函数和复制赋值操作符而改为提供虚拟的 Clone 成员函数。


第 55 条 使用赋值的标准形式


第 56 条 只要可行,就提供不会失败的 swap(而且要正确地提供)


名字空间与模块


第 57 条 将类型及其非成员函数接口置于同一名字空间中

非成员也是函数:如果要将非成员函数(特别是操作符和辅助函数)设计成类 X 的接口的一部分,那么就必须与 X 相同的名字空间中定义它们,以便正确调用。


第 58 条 应该将类型和函数分别置于不同的名字空间中,除非有意想让它们一起工作


第 59 条 不要在头文件中或者 #include 之前编写名字空间 using

不要在 #include 之前编写 using 指令;

头文件中,不要编写名字空间级的 using 指令或 using 声明,应该显式的用名字空间限定所有的名字。


第 60 条 要避免在不同的模块中分配和释放内存

在一个模块中分配内存,而在另一个模块中释放它,会在这两个模块之间产生微妙的远距离依赖使程序变得脆弱。


第 61 条 不要在头文件中定义具有链接的实体

具有链接的实体,包括名字空间级的变量或函数,都需要分配内存。在头文件中定义这样的实体将导致链接时错误或者内存的浪费。应将所有具有链接的实体放入实现文件。


第 62 条 不要允许异常跨越模块边界传播

C++异常处理没有普遍通用的二进制标准。不要在两段代码之间传播异常,除非能够控制用来构建两段代码的编译器和编译选项;否则模块可能无法支持可兼容地实现异常传播。


第 63 条 在模块的接口中使用具有良好可移植性的类型


模板与泛型


第 64 条 理智地结合静态多态性和动态多态性


第 65 条 有意地进行显式自定义

编写模板时,应该有意地、正确地提供自定义点,并清晰地记入文档。在使用模板时,应该了解模板想要你如何进行自定义以将其用于你的类型,并且正确地自定义。


第 66 条 不要特化函数模板


第 67 条 不要无意地编写不通用的代码

依赖抽象而非细节:使用最通用、最抽象的方法来实现一个功能。


错误处理与异常第 68 条 广泛地使用断言记录内部假设和不变式

一个事件中所含的信息量与该事件发生的概率是成反比的。如果 assert 触发的可能性越低,它触发时所提供的信息量就越大。尽量使用 assert(!”informational message”)。


第 69 条 建立合理的错误处理策略,并严格遵守

应该在设计早期开发实际、一致、合理的错误处理策略,并予以严格遵守。


第 70 条 区别错误与非错误

错误就是阻止函数成功操作的任何失败。


第 71 条 设计和编写错误安全代码


第 72 条 优先使用异常报告错误

出现问题时,就使用异常:应该使用异常而不是错误码来报告错误。

异常处理很自然地将错误检查和恢复都放进了独立的 catch 代码块,使错误处理变得清晰有形。


第 73 条 通过值抛出,通过引用捕获

通过值(而非指针)抛出异常,通过引用(通常是 const 的引用)捕获异常。重新抛出相同的异常时,优先使用 throw,避免使用 throw e;。


第 74 条 正确地报告、处理和转换错误


第 75 条 避免使用异常规范


STL:容器


第 76 条 默认时使用 vector。否则,选择其他合适的容器

意思就是,在使用数组、链表等数据结构时,优先使用标准库;然后选择你认为的最好的容器即可。


第 77 条 用 vector 和 string 代替数组

不要使用 C 语言风格的数组、指针运算和内存管理原语操作实现数组抽象。使用 vector 或者 string 不仅更轻松,而且还有助于编写更安全、伸缩性更好的软件。


第 78 条 使用 vector(和 string::c_str)与非 C++ API 交换数据


第 79 条 在容器中只存储值和智能指针

在容器中存储值对象:容器假设他们所存放的是类似值的类型,包括值类型、智能指针和迭代器。


第 80 条 用 push_back 代替其他扩展序列的方式


第 81 条 多用范围操作,少用单元素操作

调用范围操作通常更易于编写,也更易于阅读,而且比显式循环的效率更高。


第 82 条 使用公认的惯用法真正地压缩容量,真正地删除元素

要真正地压缩容器的多余容量,应该使用“swap 魔术”惯用法。要真正地删除容器中的元素,应该使用 erase-remove 惯用法。


STL:算法

第 83 条 使用带检查的 STL 实现


第 84 条 用算法调用代替手工编写的循环

编写算法调用代替手工编写的循环,可以使表达力更强、维护性更好、更不易出错,而且同样高效。

算法的正确性也很可能比循环好。手工编写的循环很容易犯使用无效迭代器这样的错误。

算法的效率经常比原始循环要好。我们所使用的标准算法是由实现标准容器的那些人实现的,就凭他们对内幕的了解,所编写的算法的效率,就绝非你我编写的任何版本所能相提并论。但是最重要的还在于,许多算法的实现精巧绝伦,你我这样的一线程序员手工编写的代码也是不可能与之一较短长的。


第 85 条 使用正确的 STL 查找算法

正确的查找方式应该使用 STL(虽然比光速慢,但已经非常快了)。

查找无序范围,应使用 find/find_if 或者 count/count_if。查找有序范围,应使用 lower_bound/upper_bound/equal_range 或 binary_search。


第 86 条 使用正确的 STL 排序算法


第 87 条 使谓词成为纯函数


保持谓词纯洁性:谓词就是返回是或否的函数对象。


不要让谓词保存或访问对其 operator()结果有影响的状态,包括成员状态和全局状态。应该使 operator()成为谓词的 const 成员函数。


第 88 条 算法和比较器的参数应多用函数对象少用函数

对象的适配性比函数好:应该向算法传递函数对象,而非函数。关联容器的比较器必须是函数对象。函数对象的适配性好,而且与直觉相反,他们产生的代码一般比函数要快。


第 89 条 正确编写函数对象

函数对象模仿的就是函数指针。与函数指针一样,一般函数对象应该通过值来传递。所有标准算法都是通过值来传递对象的,我们自己的算法也应如此。


类型安全


第 90 条 避免使用类型分支,多使用多态

避免通过对象类型分支来定制行为。使用模板和虚函数,让类型自己来决定行为。


第 91 条 依赖类型,而非其表示方式


第 92 条 避免使用 reinterpret_cast

不要尝试使用 reinterpret_cast 强制编译器将某个类型对象的内存表示重新解释成另一种类型的对象。这违反了维护类型安全性的原则,尤其可怕的是,reinterpret_cast 甚至不能保证是否能够达到这一目的,也无法保证其他功能。


第 93 条 避免对指针使用 static_cast

不要对动态对象的指针使用 static_cast:安全的替代方法有很多,包括使用 dynamic_cast,重构,乃至重新设计。


第 94 条 避免强制转换 const


第 95 条 不要使用 C 风格的强制转换


C 语言风格的强制转换根据上下文具有不同的语义,而所有这些都隐藏在相同的语法背后。用 C++风格的强制转换代替 C 风格的强制转换有助于防范意想不到的错误。


第 96 条 不要对非 POD 进行 memcpy 操作或者 memcmp 操作


第 97 条 不要使用联合重新解释表示方式


第 98 条 不要使用可变长参数(...)

要避免使用可变长参数,应改用高级的 C++结构和库。


第 99 条 不要使用失效对象。不要使用不安全函数


第 100 条 不要多态地处理数组

发布于: 刚刚阅读数: 3
用户头像

老王同学

关注

多读书,勤跑步,少做梦 2020-04-30 加入

还未添加个人简介

评论

发布
暂无评论
《C++编程规范-101条规则 准则与最佳实践》读书笔记_c++_老王同学_InfoQ写作社区