C++编程规范 设计风格

第5条 一个实体应该只有一个紧凑的职责
一次只解决一个问题:只给一个实体(变量、类、函数、名称空间、模块和库)赋予一个定义良好的职责。随着实体变大,其职责范围自然也会扩大,但是职责不应该发散。
详细:
1、“多个职责”经常意味着“多重性格”——可能的行为和状态的各种组合方式。
2、应该用较小的低层抽象构建更高层次的抽象。
3、要避免将几个低层抽象集合成一个较大的低层次抽象聚合体。
4、C 语言中的 realloc 和 C++ 语言中的 basic_string 就是臭名昭著的不良设计。

第6条 正确、简单和清晰第一
软件简单为美(Keep It Simple Software, KISS):正确优于速度。简单优于复杂。清晰优于机巧。安全优于不安全(见 C83, C99)。
详细:
1、代码的维护者将因为你编写的代码容易理解而感谢你——而且这个维护者往往就是未来的你,要努力回忆起6个月前的所思所想。
2、当(不是假如)你想为了性能而进行不成熟的优化因而影响了清晰性时,请回想一下第8条的要点:使一个正确的程序变快,比使一个快速的程序正确要容易得多。
3、要避免使用程序设计语言中的冷僻特性。应该使用最简单的有效技术。
4、不要使用不必要的或者小聪明式的操作符重载。
5、应该使用命名变量,而不要使用临时变量,作为构造函数的参数。

第7条 编程中应知道何时和如何考虑可伸缩性
小心数据的爆炸性增长:不要进行不成熟的优化,但是要密切关注渐近复杂性。处理用户数据的算法对所处理的数据量耗费的时间应该是可预测的,最好不差于线性关系。如果能够证明优化必要而且非常重要,尤其在数据量逐渐增长的情况下,那么应该集中精力改善算法的 O(N) 复杂性,而不是进行小型的优化,比如节省一个多余的加法运算。
详细:
1、即使不知道数据量是否会大到成为某个特定计算的问题,默认情况下也应该避免使用不能很好地应付用户数据量(可能增加)的算法,除非这种伸缩性不好的算法有明显的清晰性和可读性方面的好处(见 C6)。
2、使用灵活的、动态分配的数据,不要使用固定大小的数组。
3、了解算法的实际复杂性。
4、优先使用线性算法或者尽可能快的算法。
5、尽可能避免劣于线性复杂性的算法。
6、永远不要使用指数复杂性的算法,除非你已经山穷水尽,确实别无选择。
7、如果有测试数据表明优化非常必要而且重要,尤其是在数据量不断增加的情况下,那么应该集中精力改善 O(N) 复杂性,而不是把精力花在节省一个多余加法这样的微观优化上。
8、尽可能优先使用线性(或者更好的)算法。可按如下顺序选择:
算法复杂度:常数 > 对数 > 线性 > 二次/多项式 > 指数
算法复杂度:O(C) > O(logN) > O(N) > O(NlogN) > O(N^2) > O(e^N)

第8条 不要进行不成熟的优化
拉丁谚语云,快马无需鞭策:不成熟优化的诱惑非常大,而它的无效性也同样严重。优化的第一原则就是:不要优化。优化的第二原则(仅适用于专家)是:还是不要优化。再三测试,而后优化。
详细:
1、不成熟的优化:以性能为名,使设计或代码更加复杂,从而导致可读性更差,但是并没有经过验证的性能需求(比如实际的度量数据和与目标的比较结果)作为正当理由,因此本质上对程序没有真正的好处。
2、让一个正确的程序更快速,比让一个快速的程序正确,要容易得太多、太多。
3、优化前必须进行度量;而度量之前必须确定优化的目标。在需求得到验证之前,注意力应该放在头号优先的事情上——为人编写代码。
4、当确实需要优化的时候,首先要考虑算法优化(见 C7),并尝试将优化封装和模块化(比如,用一个函数或者类,见 C5, C11),然后在注释中清楚地说明优化的原因并列出所用算法作为参考。

第9条 不要进行不成熟的劣化
放松自己,轻松编程:在所有其他事情特别是代码复杂性和可读性都相同的情况下,一些高效的设计模式和编程惯用法会从你的指尖自然流出,而且不会比悲观的替代方案更难写。这并不是不成熟的优化,而是避免不必要的劣化(pessimization)。
详细:
1、不成熟的劣化:指的是编写如下这些没有必要的、可能比较低效的程序:在可以用通过引用传递的时候,却定义了通过值传递的参数(见 C25);在使用前缀 ++ 操作符很合适的场合,却使用后缀版本(见 C28);在构造函数中使用赋值操作而不是初始化列表(见 C48)。
2、构造既清晰又有效的程序有两种重要的方式:使用抽象(见 C11, C36)和库(见 C84)。
3、更需要关注的是可伸缩性,而不是挤掉一个小小的循环。

第10条 尽量减少全局和共享数据
共享会导致冲突:避免共享数据,尤其是全局数据。共享数据会增加耦合度,从而降低可维护性,通常还会降低性能。
详细:
1、避免使用名字空间作用域中具有外部连接的数据或者作为静态类成员的数据。
2、全局名字空间中的对象名称还会污染全局名字空间。
3、初始化顺序规则是非常难于掌握的,应该尽量避免使用;如果不得不用的话,则应该充分了解,小心使用。
4、名字空间作用域中的对象、静态成员对象或者跨线程或跨进程共享的对象会减少多线程和多处理器环境中的并行性,往往是产生性能和可伸缩性瓶颈的源头(见 C7)。为“无共享”而奋斗吧,用通信方式(比如消息队列)代替数据共享。

第11条 隐藏信息
不要泄密:不要公开提供抽象的实体的内部信息。
详细:
1、为了尽量减少操作抽象的调用代码和抽象的实现之间的依赖性,必须隐藏实现内部的数据。
2、信息隐藏限制了变化的影响范围、强化了不变式(见 C41)。
3、绝对不要将类的数据成员设为 public(见 C41),或者公开指向它们的指针或句柄(见 C42)而使其公开。
4、它同样适用于更大的实体比如程序库。
5、测试代码进行白盒测试需要则是例外。
6、值的聚合(struct 类型)并没有提供任何抽象也是例外。

第12条 懂得何时和如何进行并发性编程
线程安全:如果应用程序使用了多个线程或者进程,应该知道如何尽量减少共享对象(见 C10),以及如何安全地共享必须共享的对象。
详细:
1、参考目标平台的文档,了解该平台的同步化原语。
2、最好将平台的原语用自己设计的抽象包装起来。
3、确保正在使用的类型在多线程程序中使用是安全的。保证非共享的对象独立;记载调用者在不同线程中使用该类型的同一个对象需要做什么。
4、外部加锁:调用者负责加锁。
5、内部加锁:每个对象将所有对自己的访问串行化,通常采用为每个公用成员函数加锁的方法来实现,这样调用者就可以不用串行化对象的使用了。只有在知道了以下两件事情之后这个选项才适用:第一,必须事先知道该类型的对象几乎总是要被跨线程共享的,否则到头来只不过进行了无效加锁。第二,必须事先知道成员函数级加锁的粒度是合适的,而且能满足大多数调用者的需要。具体而言,类型接口的设计应该有利于粗粒度的、自给自足的操作。
6、不加锁的设计,包括不变性(只读对象):无需加锁。
7、在获取多个锁时,通过安排所有获取同样的锁的代码以相同的顺序获取锁,可以避免死锁情况的发生。(释放锁可以按照任意顺序进行。)解决方案之一,是按内存地址的升序获取锁,地址恰好提供了一个方便、唯一而且是应用程序范围的排序。

第13条 确保资源为对象所拥有。使用显式的 RAII 和智能指针
利器在手,不要再徒手为之:C++ 的“资源获取即初始化”(resource acquisition is initialization, RAII)惯用法是正确处理资源的利器。RAII 使编译器能够提供强大且自动的保证,这在其他语言中可是需要脆弱的手工编写的惯用法才能实现的。分配原始资源的时候,应该立即将其传递给属主对象。永远不要在一条语句中分配一个以上的资源。
详细:
1、具有资源获取的构造函数和具有资源释放的析构函数的基于栈(或引用计数)的对象成为了自动化资源管理和清除的极佳工具。
2、每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,让对象为我们强制配对,并在其析构函数中执行资源释放。
3、绝对不要在一条语句中分配一个以上的资源,应该在自己的代码语句中执行显式的资源分配(比如 new),而且每次都应该马上将分配的资源赋予管理对象(比如 shared_ptr)。
4、如果被指向的对象只对有限的代码(比如纯粹在类的内部,诸如一个 Tree 类的内部节点导航指针)可见,那么原始指针就够用了。

返回 目录

返回《C++ 编程规范及惯用法》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值