C++ 编程规范

谁说我不能喝 提交于 2020-04-07 10:08:19

组织和策略问题

 

第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条 优先使用算术操作符和赋值操作符的标准形式

            a+b 定义为 a+=b          

 

第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条 不要多态地处理数组

   数组的可调整性很差:多态地处理数组是绝对的类型错误,而且编译器有可能不会做出任何提示。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!