一些基本的设计模式综述

设计模式分为三种类型,共23类。   

创建型模式:单件模式、抽象工厂模式、建造者模式、工厂方法、原型模式。
   
结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。  
 
行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。  
 
   
Abstract Factory(抽象工厂模式):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
 
Adapter(适配器模式):将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。   
 
Bridge(桥接模式):将抽象部分与它的实现部分分离,使它们都可以独立地变化。  
 
Builder(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 
   
Chain of Responsibility(职责链模式):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。   
 
Command(命令模式):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。 
   
Composite(组合模式):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。   
 
Decorator(装饰模式):动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活
   
Facade(外观模式):为子系统中的一组接口提供一个一致的界面, F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。  
 
Factory Method(工厂模式):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。  
 
Flyweight(享元模式):运用共享技术有效地支持大量细粒度的对象。 
   
Interpreter(解析器模式):给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。   
 
Iterator迭代器):提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。  
 
Mediator(中介模式):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。   
 
Memento(备忘录模式):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态
   
Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。   
 
Prototype(原型模式):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。   
 
Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问[1]。   
 
Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。 
   
State(状态模式):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 
   
Strategy(策略模式):定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。   
 
Template Method(模板方法模式):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。  
 
Visitor(访问者模式):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

设计模式的原则

  为什么要提倡”Design Pattern”呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?面向对象有几个原则:开闭原则(Open Closed Principal,OCP)、里氏代换原则(LSP)、依赖倒转原则(DIP)、接口隔离原则(ISP)以及抽象类(Abstract Class)、接口(Interface)。开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法   设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。
开放封闭原则

 

  此原则是由”Bertrand Meyer”提出的。原文是:”Software entities should be open for extension,but closed for modification”。就是说模块应对扩展开放,而对修改关闭。模块应尽量在不修改原(是”原”,指原来的代码)代码的情况下进行扩展。那么怎么扩展呢?我们看工厂模式”factory pattern”:假设中关村有一个卖盗版盘和毛片的小子,我们给他设计一”光盘销售管理软件”。我们应该先设计一”光盘”接口。如图:   

[pre]______________   
|<>|   
| 光盘 |   
|_____________|   
|+卖() |   
| |   
|_____________|[/pre]   
而盗版盘和毛片是其子类。小子通过”DiscFactory”来管理这些光盘。代码为:   
public class DiscFactory{   
               public static 光盘 getDisc(String name)
               {   
                    return (光盘)Class.forName(name).getInstance();   
               }   
}   
有人要买盗版盘,怎么实现呢?   
public class 小子{   
               public static void main(String[] args)
               {   
                    光盘 d=DiscFactory.getDisc(“盗版盘”);   
                    d.卖();   
               }   
}   
如果有一天,这小子良心发现了,开始卖正版软件。没关系,我们只要再创建一个”光盘”的子类”正版软件”就可以了。不需要修改原结构和代码。怎么样?对扩展开放,对修改关闭。”开-闭原则”   工厂模式是对具体产品进行扩展,有的项目可能需要更多的扩展性,要对这个”工厂”也进行扩展,那就成了”抽象工厂模式“。
 
里氏代换原则

 

  里氏代换原则是由”Barbara Liskov”提出的。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。如果调用的是父类的话,那么换成子类也完全可以运行。比如:   

          光盘 d=new 盗版盘();   
          d.卖();   
          现在要将”盗版盘”类改为”毛片”类,没问题,完全可以运行。Java编译程序会检查程序是否符合里氏代换原则。还记得java继承的一个原则吗?子类override方法的访问权限不能小于父类对应方法的访问权限。比如”光盘”中的方法”卖”访问权限是”public”,那么”盗版盘”和”毛片”中的”卖”方法就不能是protected或private,编译不能通过。为什么要这样呢?你想啊:如果”盗版盘”的”卖”方法是private。那么下面这段代码就不能执行了:   
          光盘 d=new 盗版盘();   
          d.卖();   
          可以说:里氏代换原则是继承复用的一个基础。
          
  
合成/聚合复用原则

 

  合成/聚合复用原则(Composite/Aggregate Reuse Principle ,CARP)经常又叫做合成复用原则。合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对的委派达到复用已有功能的目的。它的设计原则是;要尽量使用合成/聚合,尽量不要使用继承。   就是说要少用继承,多用合成关系来实现。我曾经这样写过程序:有几个类要与数据库打交道,就写了一个数据库操作的类,然后别的跟数据库打交道的类都继承这个。结果后来,我修改了数据库操作类的一个方法,各个类都需要改动。”牵一发而动全身”!面向对象是要把波动限制在尽量小的范围。   在Java中,应尽量针对Interface编程,而非实现类。这样,更换子类不会影响调用它方法的代码。要让各个类尽可能少的跟别人联系,”不要与陌生人说话”。这样,城门失火,才不至于殃及池鱼。扩展性和维护性才能提高   理解了这些原则,再看设计模式,只是在具体问题上怎么实现这些原则而已。张无忌学太极拳,忘记了所有招式,打倒了”玄冥二老”,所谓”心中无招”。设计模式可谓招数,如果先学通了各种模式,又忘掉了所有模式而随心所欲,可谓OO之最高境界。呵呵,搞笑,搞笑!(JR)

 
依赖倒转原则

 

  抽象不应该依赖于细节,细节应当依赖于抽象。   要针对接口编程,而不是针对实现编程。   传递参数,或者在组合聚合关系中,尽量引用层次高的类。   主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉。

     开闭原则的主要机制就是依赖倒转原则,这个原则的内容是:要依赖于抽象,不要依赖于具体,即要针对接口编程,不针对实现编程。  
     依赖也就是耦合,共分为下面3种。  
     零耦合(Nil Coupling)关系:两个类没有依赖关系。  
     具体耦合(Concrete Coupling)关系:两个具体的类之间有依赖关系,如果一个具体类直接引用另外一个具体类,就是这种关系。  
     抽象耦合(Abstract Coupling)关系:这种关系发生在一个具体类和一个抽象类之间,这样就使必须发生关系的类之间保持最大的灵活性。  
     依赖倒转原则要求客户端依赖于抽象耦合,抽象不应当依赖于细节,细节应当依赖于抽象。这个原则的另外一个表述就是:要针对接口编程,不要对实现编程。程序在需要引用一个对象时,应当尽可能地使用抽象类型作为变量的静态类型,这就是针对接口编程的含义。依赖倒转原则是达到开闭原则的途径。  
     要做到依赖倒转原则,使用抽象方式耦合是关键。由于一个抽象耦合总要涉及具体类从抽象类继承,并且需要保证在任何引用到某类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础,依赖倒转原则是OOD的核心原则,设计模式的研究和应用都是用它作为指导原则的。
 
接口隔离原则
       定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干
           使用多个专门的接口比使用单一的总接口要好。  
          一个类对另外一个类的依赖性应当是建立在最小的接口上的。  
          一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
 
抽象类
 
  抽象类不会有实例,一般作为父类为子类继承,一般包含这个系的共同属性和方法。   注意:好的继承关系中,只有叶节点是具体类,其他节点应该都是抽象类,也就是说具体类   是不被继承的。将尽可能多的共同代码放到抽象类中。
 
迪米特法则
 
    最少知识原则,就是说一个对象应当对其他对象有尽可能少的了解。不要和陌生人说话。
       迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。  
   迪米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。外观模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。

转:Google C++ 编程

  早上起来看微博,看到大神们又在关于C++的各种讨论,找相关知识来看看,搜到了这篇文章。

Google有很多自己实现的使C++代码更加健壮的技巧、功能,以及有异于别处的C++的使用方式。

1. 智能指针(Smart Pointers)

如果确实需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中对象,你应该只使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。

“智能”指针看上去是指针,其实是附加了语义的对象。以scoped_ptr为例,scoped_ptr被销毁时,删除了它所指向的对象。shared_ptr也是如此,而且,shared_ptr实现了引用计数(reference-counting),从而只有当它所指向的最后一个对象被销毁时,指针才会被删除。

一般来说,我们倾向于设计对象隶属明确的代码,最明确的对象隶属是根本不使用指针,直接将对象作为一个域(field)或局部变量使用。另一种极端是引用计数指针不属于任何对象,这样设计的问题是容易导致循环引用或其他导致对象无法删除的诡异条件,而且在每一次拷贝或赋值时连原子操作都会很慢。

虽然不推荐这么做,但有些时候,引用计数指针是最简单有效的解决方案。

译者注:看来,Google所谓的不同之处,在于尽量避免使用智能指针:D,使用时也尽量局部化,并且,安全第一。

  • 其他C++特性

1. 引用参数(Reference Arguments)

所以按引用传递的参数必须加上const。

定义:在C语言中,如果函数需要修改变量的值,形参(parameter)必须为指针,如int foo(int *pval)。在C++中,函数还可以声明引用形参:int foo(int &val)。

优点:定义形参为引用避免了像(*pval)++这样丑陋的代码,像拷贝构造函数这样的应用也是必需的,而且不像指针那样不接受空指针NULL。

缺点:容易引起误解,因为引用在语法上是值却拥有指针的语义。

结论:

函数形参表中,所有引用必须是const:

 

 

void Foo(const string &in, string *out);

 

 

事实上这是一个硬性约定:输入参数为值或常数引用,输出参数为指针;输入参数可以是常数指针,但不能使用非常数引用形参。

在强调参数不是拷贝而来,在对象生命期内必须一直存在时可以使用常数指针,最好将这些在注释中详细说明。bind2nd和mem_fun等STL适配器不接受引用形参,这种情况下也必须以指针形参声明函数。

2. 函数重载(Function Overloading)

仅在输入参数类型不同、功能相同时使用重载函数(含构造函数),不要使用函数重载模仿缺省函数参数。

定义:可以定义一个函数参数类型为const string&,并定义其重载函数类型为const char*。

 

 

class MyClass {
public:
  void Analyze(const string &text);
  void Analyze(const char *text, size_t textlen);
};

 

 

优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重载,同时为访问者带来便利。

缺点:限制使用重载的一个原因是在特定调用处很难确定到底调用的是哪个函数,另一个原因是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。此外在阅读库的客户端代码时,因缺省函数参数造成不必要的费解。

结论:如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用AppendString()、AppendInt()而不是Append()。

3. 缺省参数(Default Arguments)

禁止使用缺省函数参数。

优点:经常用到一个函数带有大量缺省值,偶尔会重写一下这些值,缺省参数为很少涉及的例外情况提供了少定义一些函数的方便。

缺点:大家经常会通过查看现有代码确定如何使用API,缺省参数使得复制粘贴以前的代码难以呈现所有参数,当缺省参数不适用于新代码时可能导致重大问题。

结论:所有参数必须明确指定,强制程序员考虑API和传入的各参数值,避免使用可能不为程序员所知的缺省参数。

4. 变长数组和alloca(Variable-Length Arrays and alloca())

禁止使用变长数组和alloca()。

优点:变长数组具有浑然天成的语法,变长数组和alloca()也都很高效。

缺点:变长数组和alloca()不是标准C++的组成部分,更重要的是,它们在堆栈(stack)上根据数据分配大小可能导致难以发现的内存泄漏:“在我的机器上运行的好好的,到了产品中却莫名其妙的挂掉了”。

结论:

使用安全的分配器(allocator),如scoped_ptr/scoped_array。

5. 友元(Friends)

允许合理使用友元类及友元函数。

通常将友元定义在同一文件下,避免读者跑到其他文件中查找其对某个类私有成员的使用。经常用到友元的一个地方是将FooBuilder声明为Foo的友元,FooBuilder以便可以正确构造Foo的内部状态,而无需将该状态暴露出来。某些情况下,将一个单元测试用类声明为待测类的友元会很方便。

友元延伸了(但没有打破)类的封装界线,当你希望只允许另一个类访问某个成员时,使用友元通常比将其声明为public要好得多。当然,大多数类应该只提供公共成员与其交互。

6. 异常(Exceptions

不要使用C++异常。

优点:

1) 异常允许上层应用决定如何处理在底层嵌套函数中发生的“不可能发生”的失败,不像出错代码的记录那么模糊费解;

2) 应用于其他很多现代语言中,引入异常使得C++与Python、Java及其他与C++相近的语言更加兼容;

3) 许多C++第三方库使用异常,关闭异常将导致难以与之结合;

4) 异常是解决构造函数失败的唯一方案,虽然可以通过工厂函数(factory function)或Init()方法模拟异常,但他们分别需要堆分配或新的“非法”状态;

5) 在测试框架(testing framework)中,异常确实很好用。

缺点:

1) 在现有函数中添加throw语句时,必须检查所有调用处,即使它们至少具有基本的异常安全保护,或者程序正常结束,永远不可能捕获该异常。例如:if f() calls g() calls h()h抛出被f捕获的异常,g就要当心了,避免没有完全清理;

2) 通俗一点说,异常会导致程序控制流(control flow)通过查看代码无法确定:函数有可能在不确定的地方返回,从而导致代码管理和调试困难,当然,你可以通过规定何时何地如何使用异常来最小化的降低开销,却给开发人员带来掌握这些规定的负担;

3) 异常安全需要RAII和不同编码实践。轻松、正确编写异常安全代码需要大量支撑。允许使用异常;

4) 加入异常使二进制执行代码体积变大,增加了编译时长(或许影响不大),还可能增加地址空间压力;

5) 异常的实用性可能会刺激开发人员在不恰当的时候抛出异常,或者在不安全的地方从异常中恢复,例如,非法用户输入可能导致抛出异常。如果允许使用异常会使得这样一篇编程风格指南长出很多(译者注,这个理由有点牵强:-()!

结论:

从表面上看,使用异常利大于弊,尤其是在新项目中,然而,对于现有代码,引入异常会牵连到所有依赖代码。如果允许异常在新项目中使用,在跟以前没有使用异常的代码整合时也是一个麻烦。因为Google现有的大多数C++代码都没有异常处理,引入带有异常处理的新代码相当困难。

鉴于Google现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一点,迁移过程会比较慢,也容易出错。我们也不相信异常的有效替代方案,如错误代码、断言等,都是严重负担。

我们并不是基于哲学或道德层面反对使用异常,而是在实践的基础上。因为我们希望使用Google上的开源项目,但项目中使用异常会为此带来不便,因为我们也建议不要在Google上的开源项目中使用异常,如果我们需要把这些项目推倒重来显然不太现实。

对于Windows代码来说,这一点有个例外(等到最后一篇吧:D)。

译者注:对于异常处理,显然不是短短几句话能够说清楚的,以构造函数为例,很多C++书籍上都提到当构造失败时只有异常可以处理,Google禁止使用异常这一点,仅仅是为了自身的方便,说大了,无非是基于软件管理成本上,实际使用中还是自己决定。

7. 运行时类型识别(Run-Time Type Information, RTTI

我们禁止使用RTTI。

定义:RTTI允许程序员在运行时识别C++类对象的类型。

优点:

RTTI在某些单元测试中非常有用,如在进行工厂类测试时用于检验一个新建对象是否为期望的动态类型。

除测试外,极少用到。

缺点:运行时识别类型意味著设计本身有问题,如果你需要在运行期间确定一个对象的类型,这通常说明你需要重新考虑你的类的设计。

结论:

除单元测试外,不要使用RTTI,如果你发现需要所写代码因对象类型不同而动作各异的话,考虑换一种方式识别对象类型。

虚函数可以实现随子类类型不同而执行不同代码,工作都是交给对象本身去完成。

如果工作在对象之外的代码中完成,考虑双重分发方案,如Visitor模式,可以方便的在对象本身之外确定类的类型。

如果你认为上面的方法你掌握不了,可以使用RTTI,但务必请三思,不要去手工实现一个貌似RTTI的方案(RTTI-like workaround),我们反对使用RTTI,同样反对贴上类型标签的貌似类继承的替代方案(译者注,使用就使用吧,不使用也不要造轮子:D)。

8. 类型转换(Casting

使用static_cast<>()等C++的类型转换,不要使用int y = (int)xint y = int(x);

定义:C++引入了有别于C的不同类型的类型转换操作。

优点:C语言的类型转换问题在于操作比较含糊:有时是在做强制转换(如(int)3.5),有时是在做类型转换(如(int)"hello")。另外,C++的类型转换查找更容易、更醒目。

缺点:语法比较恶心(nasty)

结论:使用C++风格而不要使用C风格类型转换。

1) static_cast:和C风格转换相似可做值的强制转换,或指针的父类到子类的明确的向上转换;

2) const_cast:移除const属性;

3) reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做一切了然于心时使用;

4) dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷(参考RTTI)。

9. 流(Streams

只在记录日志时使用流。

定义:流是printf()scanf()的替代。

优点:有了流,在输出时不需要关心对象的类型,不用担心格式化字符串与参数列表不匹配(虽然在gcc中使用printf也不存在这个问题),打开、关闭对应文件时,流可以自动构造、析构。

缺点:流使得pread()等功能函数很难执行,如果不使用printf之类的函数而是使用流很难对格式进行操作(尤其是常用的格式字符串%.*s),流不支持字符串操作符重新定序(%1s),而这一点对国际化很有用。

结论:

不要使用流,除非是日志接口需要,使用printf之类的代替。

使用流还有很多利弊,代码一致性胜过一切,不要在代码中使用流。

拓展讨论:

对这一条规则存在一些争论,这儿给出深层次原因。回忆唯一性原则(Only One Way):我们希望在任何时候都只使用一种确定的I/O类型,使代码在所有I/O处保持一致。因此,我们不希望用户来决定是使用流还是printf + read/write,我们应该决定到底用哪一种方式。把日志作为例外是因为流非常适合这么做,也有一定的历史原因。

流的支持者们主张流是不二之选,但观点并不是那么清晰有力,他们所指出流的所有优势也正是其劣势所在。流最大的优势是在输出时不需要关心输出对象的类型,这是一个亮点,也是一个不足:很容易用错类型,而编译器不会报警。使用流时容易造成的一类错误是:

cout << this;  // Prints the addresscout << *this;  // Prints the contents

 

编译器不会报错,因为<<被重载,就因为这一点我们反对使用操作符重载。

有人说printf的格式化丑陋不堪、易读性差,但流也好不到哪儿去。看看下面两段代码吧,哪个更加易读?

cerr << "Error connecting to '" << foo->bar()->hostname.first     << ":" << foo->bar()->hostname.second << ": " << strerror(errno);fprintf(stderr, "Error connecting to '%s:%u: %s",        foo->bar()->hostname.first, foo->bar()->hostname.second,        strerror(errno));

 

你可能会说,“把流封装一下就会比较好了”,这儿可以,其他地方呢?而且不要忘了,我们的目标是使语言尽可能小,而不是添加一些别人需要学习的新的内容。

每一种方式都是各有利弊,“没有最好,只有更好”,简单化的教条告诫我们必须从中选择其一,最后的多数决定是printf + read/write

10. 前置自增和自减(Preincrement and Predecrement

对于迭代器和其他模板对象使用前缀形式(++i)的自增、自减运算符。

定义:对于变量在自增(++ii++)或自减(--ii--)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增自减。

优点:不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i进行一次拷贝,如果i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式动作一样(译者注,不考虑表达式的值,相信你知道我在说什么),为什么不直接使用前置自增呢?

缺点:C语言中,当表达式的值没有使用时,传统的做法是使用后置自增,特别是在for循环中,有些人觉得后置自增更加易懂,因为这很像自然语言,主语(i)在谓语动词(++)前。

结论:对简单数值(非对象)来说,两种都无所谓,对迭代器和模板类型来说,要使用前置自增(自减)。

11. const的使用(Use of const

我们强烈建议你在任何可以使用的情况下都要使用const

定义:在声明的变量或参数前加上关键字const用于指明变量值不可修改(如const int foo),为类中的函数加上const限定表明该函数不会修改类成员变量的状态(如class Foo { int Bar(char c) const; };)。

优点:人们更容易理解变量是如何使用的,编辑器可以更好地进行类型检测、更好地生成代码。人们对编写正确的代码更加自信,因为他们知道所调用的函数被限定了能或不能修改变量值。即使是在无锁的多线程编程中,人们也知道什么样的函数是安全的。

缺点:如果你向一个函数传入const变量,函数原型中也必须是const的(否则变量需要const_cast类型转换),在调用库函数时这尤其是个麻烦。

结论const变量、数据成员、函数和参数为编译时类型检测增加了一层保障,更好的尽早发现错误。因此,我们强烈建议在任何可以使用的情况下使用const

1) 如果函数不会修改传入的引用或指针类型的参数,这样的参数应该为const

2) 尽可能将函数声明为const,访问函数应该总是const,其他函数如果不会修改任何数据成员也应该是const,不要调用非const函数,不要返回对数据成员的非const指针或引用;

3) 如果数据成员在对象构造之后不再改变,可将其定义为const

然而,也不要对const过度使用,像const int * const * const x;就有些过了,即便这样写精确描述了x,其实写成const int** x就可以了。

关键字mutable可以使用,但是在多线程中是不安全的,使用时首先要考虑线程安全。

const位置

有人喜欢int const *foo形式不喜欢const int* foo,他们认为前者更加一致因此可读性更好:遵循了const总位于其描述的对象(int)之后的原则。但是,一致性原则不适用于此,“不要过度使用”的权威抵消了一致性使用。将const放在前面才更易读,因为在自然语言中形容词(const)是在名词(int)之前的。

这是说,我们提倡const在前,并不是要求,但要兼顾代码的一致性!

12. 整型(Integer Types

C++内建整型中,唯一用到的是int,如果程序中需要不同大小的变量,可以使用<stdint.h>中的精确宽度(precise-width)的整型,如int16_t

定义:C++没有指定整型的大小,通常人们认为short是16位,int是32位,long是32位,long long是64位。

优点:保持声明统一。

缺点:C++中整型大小因编译器和体系结构的不同而不同。

结论

<stdint.h>定义了int16_tuint32_tint64_t等整型,在需要确定大小的整型时可以使用它们代替shortunsigned long long等,在C整型中,只使用int。适当情况下,推荐使用标准类型如size_tptrdiff_t

最常使用的是,对整数来说,通常不会用到太大,如循环计数等,可以使用普通的int。你可以认为int至少为32位,但不要认为它会多于32位,需要64位整型的话,可以使用int64_tuint64_t

对于大整数,使用int64_t

不要使用uint32_t等无符号整型,除非你是在表示一个位组(bit pattern)而不是一个数值。即使数值不会为负值也不要使用无符号类型,使用断言(assertion,译者注,这一点很有道理,计算机只会根据变量、返回值等有无符号确定数值正负,仍然无法确定对错)来保护数据。

无符号整型

有些人,包括一些教科书作者,推荐使用无符号类型表示非负数,类型表明了数值取值形式。但是,在C语言中,这一优点被由其导致的bugs所淹没。看看:

for (unsigned int i = foo.Length()-1; i >= 0; --i) ...

 

上述代码永远不会终止!有时gcc会发现该bug并报警,但通常不会。类似的bug还会出现在比较有符合变量和无符号变量时,主要是C的类型提升机制(type-promotion scheme,C语言中各种内建类型之间的提升转换关系)会致使无符号类型的行为出乎你的意料。

因此,使用断言声明变量为非负数,不要使用无符号型。

13. 64位下的可移植性(64-bit Portability

代码在64位和32位的系统中,原则上应该都比较友好,尤其对于输出、比较、结构对齐(structure alignment)来说:

1) printf()指定的一些类型在32位和64位系统上可移植性不是很好,C99标准定义了一些可移植的格式。不幸的是,MSVC 7.1并非全部支持,而且标准中也有所遗漏。所以有时我们就不得不自己定义丑陋的版本(使用标准风格要包含文件inttypes.h):

// printf macros for size_t, in the style of inttypes.h#ifdef _LP64#define __PRIS_PREFIX "z"#else#define __PRIS_PREFIX#endif// Use these macros after a % in a printf format string// to get correct 32/64 bit behavior, like this:// size_t size = records.size();// printf("%"PRIuS"/n", size);#define PRIdS __PRIS_PREFIX "d"#define PRIxS __PRIS_PREFIX "x"#define PRIuS __PRIS_PREFIX "u"#define PRIXS __PRIS_PREFIX "X"#define PRIoS __PRIS_PREFIX "o" 

 

类型 不要使用 使用 备注
void *(或其他指针类型) %lx %p  
int64_t %qd%lld %"PRId64"  
uint64_t %qu%llu%llx %"PRIu64"%"PRIx64"  
size_t %u %"PRIuS"%"PRIxS" C99指定%zu
ptrdiff_t %d %"PRIdS" C99指定%zd

注意宏PRI*会被编译器扩展为独立字符串,因此如果使用非常量的格式化字符串,需要将宏的值而不是宏名插入格式中,在使用宏PRI*时同样可以在%后指定长度等信息。例如,printf("x = %30"PRIuS"/n", x)在32位Linux上将被扩展为printf("x = %30" "u" "/n", x),编译器会处理为printf("x = %30u/n", x)

2) 记住sizeof(void *) != sizeof(int),如果需要一个指针大小的整数要使用intptr_t

3) 需要对结构对齐加以留心,尤其是对于存储在磁盘上的结构体。在64位系统中,任何拥有int64_t/uint64_t成员的类/结构体将默认被处理为8字节对齐。如果32位和64位代码共用磁盘上的结构体,需要确保两种体系结构下的结构体的对齐一致。大多数编译器提供了调整结构体对齐的方案。gcc中可使用__attribute__((packed)),MSVC提供了#pragma pack()__declspec(align())(译者注,解决方案的项目属性里也可以直接设置)

4) 创建64位常量时使用LLULL作为后缀,如:

 

int64_t my_value = 0x123456789LL;uint64_t my_mask = 3ULL << 48;

 

5) 如果你确实需要32位和64位系统具有不同代码,可以在代码变量前使用。(尽量不要这么做,使用时尽量使修改局部化)。

14. 预处理宏(Preprocessor Macros

使用宏时要谨慎,尽量以内联函数、枚举和常量代替之。

宏意味着你和编译器看到的代码是不同的,因此可能导致异常行为,尤其是当宏存在于全局作用域中。

值得庆幸的是,C++中,宏不像C中那么必要。宏内联效率关键代码(performance-critical code)可以内联函数替代;宏存储常量可以const变量替代;宏“缩写”长变量名可以引用替代;使用宏进行条件编译,这个……,最好不要这么做,会令测试更加痛苦(#define防止头文件重包含当然是个例外)。

宏可以做一些其他技术无法实现的事情,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如字符串化(stringifying,译者注,使用#连接(concatenation,译者注,使用##等等)。但在使用前,仔细考虑一下能不能不使用宏实现同样效果。

译者注:关于宏的高级应用,可以参考C语言宏的高级应用

下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考:

1) 不要在.h文件中定义宏;

2) 使用前正确#define,使用后正确#undef

3) 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;

4) 不使用会导致不稳定的C++构造(unbalanced C++ constructs,译者注)的宏,至少文档说明其行为。

15. 0和NULL(0 and NULL

整数用0,实数用0.0,指针用NULL,字符(串)用'/0'

整数用0,实数用0.0,这一点是毫无争议的。

对于指针(地址值),到底是用0还是NULL,Bjarne Stroustrup建议使用最原始的0,我们建议使用看上去像是指针的NULL,事实上一些C++编译器(如gcc 4.1.0)专门提供了NULL的定义,可以给出有用的警告,尤其是sizeof(NULL)和sizeof(0)不相等的情况。

字符(串)用'/0',不仅类型正确而且可读性好。

16. sizeof(sizeof

尽可能用sizeof(varname)代替sizeof(type)

使用sizeof(varname)是因为当变量类型改变时代码自动同步,有些情况下sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。

Struct data;memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));

 

17. Boost库(Boost

只使用Boost中被认可的库。

定义:Boost库集是一个非常受欢迎的、同级评议的(peer-reviewed)、免费的、开源的C++库。

优点:Boost代码质量普遍较高、可移植性好,填补了C++标准库很多空白,如型别特性(type traits)、更完善的绑定(binders)、更好的智能指针,同时还提供了TR1(标准库的扩展)的实现。

缺点:某些Boost库提倡的编程实践可读性差,像元程序(metaprogramming)和其他高级模板技术,以及过度“函数化”(”functional”)的编程风格。

结论:为了向阅读和维护代码的人员提供更好的可读性,我们只允许使用Boost特性的一个成熟子集,当前,这些库包括:

1) Compressed Pairboost/compressed_pair.hpp

2) Pointer Containerboost/ptr_container不包括ptr_array.hpp和序列化(serialization)。

我们会积极考虑添加可以的Boost特性,所以不必拘泥于该规则。

______________________________________

译者:关于C++特性的注意事项,总结一下:

1. 对于智能指针,安全第一、方便第二,尽可能局部化(scoped_ptr)

2. 引用形参加上const,否则使用指针形参;

3. 函数重载的使用要清晰、易读;

4. 鉴于容易误用,禁止使用缺省函数参数(值得商榷);

5. 禁止使用变长数组;

6. 合理使用友元;

7. 为了方便代码管理,禁止使用异常(值得商榷);

8. 禁止使用RTTI,否则重新设计代码吧;

9. 使用C++风格的类型转换,除单元测试外不要使用dynamic_cast;

10. 使用流还printf + read/write,it is a problem;

11. 能用前置自增/减不用后置自增/减;

12. const能用则用,提倡const在前;

13. 使用确定大小的整型,除位组外不要使用无符号型;

14. 格式化输出及结构对齐时,注意32位和64位的系统差异;

15. 除字符串化、连接外尽量避免使用宏;

16. 整数用0,实数用0.0,指针用NULL,字符(串)用’/0’;

17. 用sizeof(varname)代替sizeof(type);

18. 只使用Boost中被认可的库。

原文链接:http://www.cnblogs.com/vs2008_taotao/archive/2010/08/18/1801984.html

今天把Google的C++编程(50页)规范找来看了一下,真是一写代码就知道你是不是专业的啊!