C++编程规范:设计风格
第5条:一个实体应该只有一个紧凑的职责
一次只解决一个问题:只给一个实体(变量、类、函数、名字空间、模块和库)赋予一个定义良好的职责
第6条:正确、简单和清晰第一
- 软件简单为美(Keep It Simple Software, KISS):质量优于速度,简单优于复杂,清晰优于机巧,安全优于不安全
- 要避免使用程序设计语言中的冷僻特性,应该事由最简单的有效技术
- 不要使用不必要的或小聪明式的操作符重载
- 应该使用命名变量,而不是临时变量,作为构造函数的参数,这样能够避免可能的声明二义性
第7条: 在编程中应该知道何时和如何考虑可伸缩性
- 默认情况下也应该避免使用不能很好地应付用户数据量的算法,除非这种伸缩性不好的算法有明显的清晰性和可读性方面的好处
- 使用灵活的、动态分配的数据,不要使用固定大小的数组
- 优先使用线性算法或尽可能快的算法
- 尽可能避免劣于线性复杂性的算法
- 永远不要使用指数复杂性的算法
第8条:不要进行不成熟的优化
- 不成熟的优化:以性能为名,使设计或代码更加复杂,从而导致可读性更差,但是并没有经过验证的性能需求作为正当理由,因此本质上对程序没有真正的好处。
- 默认时,不要把注意力集中在如何使代码更快上:首先应该是使代码尽可能地清晰和易读
- 初学者常犯的一个错误,就是编写新代码时着迷于进行过度优化,却牺牲了代码的可理解性
- 分析器(profiler)能够通过函数命中计数告诉我们哪些函数应该但是并没有被标记为inline
第9条:不要进行不成熟的劣化
- 不成熟的劣化:在可以通过引用传递的时候,却定义了通过值传递的参数;在使用前缀操作符很合适的场合,却使用后缀版本;在构造函数中使用赋值操作而不是初始化列表
- 构造既清晰又有效的程序有两种方式:使用抽象和库
第10条:尽量减少全局和共享数据
- 避免使用命名空间作用域中具有外部连接的数据或者作为静态类成员的数据,这些数据会使程序逻辑边的更加复杂。
- 共享数据对单元测试会产生不良影响,因为使用共享数据的代码片段的正确性不仅取决于数据变化的过程,更取决于以后会使用该数据的未知代码区域的机能。
- 全局命名空间中的对象名称还会污染全名命名空间
- 如果必须使用全局的、命名空间作用域的或者静态的类对象,一定要仔细地对其进行初始化,在不同编译单元中这种对象的初始化顺序是未定义的。
- 命名空间作用域中的对象、静态成员对象或者跨线程或跨进程共享的对象会减少多线程和多处理器环境中的并行性,往往是产生性能和可伸缩性瓶颈的原因。
第11条:隐藏信息
- 为了减少操作抽象的调用代码和抽象的实现之间的依赖性,必须隐藏实现内部的数据。否则调用代码就能访问该信息,或者操作该信息的话,原本应属于内部的信息就泄露给了调用代码所依赖的抽象
- 信息隐藏的从以下两个方面降低了风险:
- 限制了变化影响的范围,缩小了变化所引起的“连锁反应”的范围,也降低了由此带来的成本
- 强化了不变式,限制了负责维护(或破坏)程序不变式的代码
- 绝对不要将类的数据成员设为Public,或者公开指向它们的指针或句柄而使其公开
- 例外:测试代码经常需要对被测试类或者模块进行白箱访问
- 例外:值的聚合只是简单地将数据绑在了一起,并没有提供任何抽象,所以不需要隐藏数据,数据本身就是借口
第12条:懂得何时和如何进行并发性编程
- 如果应用程序使用了多个线程或者进程,应该知道如何尽量减少共享对象,以及如何安全地共享必须共享的对象
- 确保正在使用的类型在多线程程序中使用是安全的:
- 保证非共享的对象独立,两个线程能够自由地使用不同的对象,无需调用者的任何特殊操作
- 记载调用者在不同线程中使用该类型的同一个对象需要做什么。
- 必须保证不同线程能够不加锁地使用该类型的不同对象(具有可修改的静态数据的类型通常不能保证这一点),而且必须在文档中说明使用者在不同线程中使用该类型的同一个对象需要做什么
- 加锁的方式:
- 外部加锁:调用者负责加锁。在这种选择下,由使用对象的代码负责了解是否跨线程共享了对象,如果是,还要负责串行化所有对该对象的使用
- 内部加锁:每个对象将所有对自己的访问串行化,通常采用为每个公有成员函数加锁的访问来实现,这样调用者就可以不用串行化对象的使用了。 内部加锁是绑定于类型的公有接口的:在类的各个单独操作本身都完整时,内部加锁才适用。
- 不加锁的设计,包括不变性(只读对象):无需加锁。
- 在获取多个锁时,通过安排所有获取同样的锁的代码以相同的顺序获取锁,可以避免死锁情况发生(释放锁则可以按照任意顺序进行)。解决方案之一,就是按照内存地址的升序获取锁,地址恰好提供了一个方便、唯一而且是应用程序范围的排序。
第13条:确保资源为对象所拥有。使用显示的RAII和智能指针
- 资源获取即初始化(Resource Acquisition Is Initialization):每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,让对象为我们强制配对,并在其析构函数中执行资源释放
- 在实现RAII时,编译器生成的复制构造函数和赋值运算符可能不正确,如果复制没有意义,可以通过将复制构造和赋值运算符设为私有并且不做定义来明确进行禁用。
- 绝对不要再一条语句中分配一条以上的资源,应该在自己的代码中执行显示的资源分配,而且每次都应该马上将分配的资源赋予管理对象。