C++编程规范之设计风格(读书笔记)

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

摘要

一次只解决一个问题:只给一个实体(变量、类、函数、名称空间、模块和库)赋予一个定义良好的职责。随着实体变大,其职责范围自然也会扩大,但是职责不应该发散。

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

摘要

软件简单为美(Keep It Simple Software,KISS):正确优于速度。简单优于复杂。清晰优于机巧。安全优于不安全(见第83条和第99条)。

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

摘要

小心数据的爆炸性增长:不要进行不成熟的优化,但是要密切关注渐近复杂性。处理用户数据的算法对所处理的数据量耗费的时间应该是可预测的,最好不差于线性关系。如果能够证明优化必要而且非常重要,尤其在数据量逐渐增长的情况下,那么应该集中精力改善算法的O(N)复杂性,而不是进行小型的优化,比如节省一个多余的加法运算。

第8条 不要进行不成熟的优化

摘要

拉丁谚语云,快马无需鞭策:不成熟优化的诱惑非常大,而它的无效性也同样严重。优化的第一原则就是:不要优化。优化的第二原则(仅适用于专家)是:还是不要优化。再三测试,而后优化。

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

摘要

放松自己,轻松编程:在所有其他事情特别是代码复杂性和可读性都相同的情况下,一些高效的设计模式和编程惯用法会从你的指尖自然流出,而且不会比悲观的替代方案更难写。这并不是不成熟的优化,而是避免不必要的劣化(pessimization)。

讨论

避免不成熟的优化并不意味着必然损害性能。所谓不成熟的劣化一词,我们指的就是编写如下这些没有必要的、可能比较低效的程序:

l     在可以用通过引用传递的时候,却定义了通过值传递的参数(见第25条)。

l     在使用前缀 ++ 操作符很合适的场合,却使用后缀版本(见第28条)。

l     在构造函数中使用赋值操作而不是初始化列表(见第48条)
构造既清晰又有效的程序有两种重要的方式:使用抽象和库

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

摘要

共享会导致冲突:避免共享数据,尤其是全局数据。共享数据会增加耦合度,从而降低可维护性,通常还会降低性能。

第11条 隐藏信息

摘要

不要泄密:不要公开提供抽象的实体的内部信息。

讨论

为了尽量减少操作抽象的调用代码和抽象的实现之间的依赖性,必须隐藏实现内部的数据。否则,调用代码就能够访问——或者更糟,操作——该信息,而原本应属于内部的信息就泄漏给了调用代码所依赖的抽象。应该公开抽象(如果有的话,还是公开领域抽象更好,但至少应该是get/set 抽象),而不是数据。

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

摘要

如果应用程序使用了多个线程或者进程,应该知道如何尽量减少共享对象(见第10条),以及如何安全地共享必须共享的对象。

请如下安全行事:
     参考目标平台的文档,了解该平台的同步化原语:典型的原语包括从轻量级的原子整数操作到内存障栅(memory barrier)[6],再到进程内和跨进程的互斥体。
     最好将平台的原语用自己设计的抽象包装起来:在需要跨平台移植性的时候,这样做尤其有益。或者,也可以使用程序库(比如pthreads [Butenhof97])为我们代劳。
     确保正在使用的类型在多线程程序中使用是安全的:说得具体一些,就是类型必须至少做到以下两个方面。
    保证非共享的对象独立:两个线程能够自由地使用不同的对象,无需调用者的任何特殊操作。
    记载调用者在不同线程中使用该类型的同一个对象需要做什么:
主要的选择有下列几个方面。
     外部加锁:调用者负责加锁。在这种选择下,由使用对象的代码负责了解是否跨线程共享了对象,而且如果是,则还要负责串行化所有对该对象的使用。例如,字符串类型通常使用外部加锁(或者不变性,见第三种选择)。
     内部加锁:每个对象将所有对自己的访问串行化,通常采用为每个公用成员函数加锁的方法来实现,这样调用者就可以不用串行化对象的使用了。例如,生产者/消费者队列通常使用内部加锁,因为它们存在的目的就是被跨线程共享,而且它们的接口就是为了在单独的成员函数调用(Push, Pop)期间能够进行适当的层次加锁而设计的。更一般的情况下,需要注意,只有在知道了以下两件事情之后这个选项才适用。
第一,必须事先知道该类型的对象几乎总是要被跨线程共享的,否则到头来只不过进行了无效加锁。请注意大多数类型都不会遇到这种情况,即使是在多线程处理分量很重的程序中,大多数对象也不会被跨线程共享(这是好现象,见第10条)。
第二,必须事先知道成员函数级加锁的粒度是合适的,而且能满足大多数调用者的需要。具体而言,类型接口的设计应该有利于粗粒度的、自给自足的操作。如果调用者总是需要对多个而不是一个操作加锁,那么就不能满足需要了,只能通过增加更多的(外部)锁,将单独加锁的函数组装成一个更大规模的已加锁工作单位。例如一个容器类型,如果它返回一个迭代器,则迭代器可能在用到之前就失效了;如果它提供find之类的能返回正确答案的成员算法,那么答案可能在用到之前就出错了;如果它的用户想要编写这样的代码:if( c.empty() ) c.push_back(x);,同样会出现问题。(更多的例子,参阅 [Sutter02]。)在这些情况下,调用者需要进行外部加锁,以获得生存期能够跨越多个单独成员函数调用的锁,这样一来每个成员函数的内部加锁就毫无用武之地了。
 
因此,内部加锁是绑定于类型的公用接口的:在类型的各个单独操作本身都完整时,内部加锁才适用;换句话说,类型的抽象级别不仅提升了,而且表达和封装得更加精确了(比如,以生产者-消费者队列的形式,而不是普通的vector)。将多个原语操作结合起来,形成粒度更粗的公开操作,不仅可以确保函数调用有意义,而且可以确保调用简单。如果原语的结合是不能确定的,而且也无法将合理的使用场景集合集中到一个命名操作中,那么有两种选择:一是使用基于回调的模型(即让调用者调用一个单独的成员函数,但是以一个命令或者函数对象的形式传入它们想要执行的任务,见第87条到第89条);二是在接口中以某种方式暴露加锁。
     不加锁的设计,包括不变性(只读对象):无需加锁。

第13条 确保资源为对象所拥有。

摘要
利器在手,不要再徒手为之:C++的“资源获取即初始化”(resource acquisition is initialization,RAII)惯用法是正确处理资源的利器。RAII使编译器能够提供强大且自动的保证,这在其他语言中可是需要脆弱的手工编写的惯用法才能实现的。分配原始资源的时候,应该立即将其传递给属主对象。永远不要在一条语句中分配一个以上的资源。 

第12和13条不是很理解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值