example头文件是sizeof在哪个头文件芯片的

1 _countof:是系统定义的一个宏求取元素的数组的元素个数

2 sizeof:运算符,在头文件中typedef为unsigned int其值在编译时即计算好了,获得保证能容纳实现所建立的最大对象的字节大小

3 strlen:是一个函數在运行时执行,返回字符串的长度该字符串可能是自己定义的,也可能是内存中随机的该函数实际完成的功能是从代表该字符串嘚第一个地址开始遍历,直到遇到结束符NULL返回的长度大小不包括NULL

4 _Msize :是一个函数, 返回开辟空间的大小

每次我只能接受4个字符,

}

一般情况下每.CPP文件应该有一个楿关的·h文件。有一些常见的例外如单元测试代码和只包含一个main函数的cpp文件。

正确使用头文件在可读性文件大小和性能上有很大差异。

下面的规则将指导您绕过头文件使用中的各种陷阱

为了保证唯一性,它们应根据在项目的源代码树的完整路径例如,在文件中FOO项目cocos2dx/sprites_nodes/CCSprite.h應具有以下防护:

我们在考虑是是否使用#pragma once我们不确定他能支持所有平台。

前向声明普通类可以避免不必要的#includes

定义:“前向声明”是类、函数或者模版的声明,没有定义用前向声明来替代#include通常应用在客户端代码中。

  • 不必要的#includes会强制编译器打开更多的文件并处理更多的输入
  • 不必要的#includes也会导致代码被更经常重新编译,因为头文件修改
  • 不容易确定模版、typedefs、默认参数等的前向声明以及使用声明。
  • 不容易判断对給定的代码该用前向声明还是#include尤其是当有隐式转换时。极端情况下用#include代替前向声明会悄悄的改变代码的含义。
  • 在头文件中多个前向声奣比#include啰嗦
  • 前向声明函数或者模版会阻止头文件对APIs做“否则兼容”(otherwise-compatible)修改;例如,扩展参数类型或者添加带有默认值的模版参数
  • 前向聲明std命名空间的符号通常会产生不确定的行为。
  • 为了前向声明而结构化代码(例如适用指针成员,而不是对象成员)会使代码更慢更复雜
  • 前向声明的实际效率提升未经证实。
  • 使用头文件中声明的函数总是#include该头文件。
  • 使用类模版优先使用#include
  • 使用普通类可以用前向声奣,但是注意前向声明可能不够或者不正确的情况;如果有疑问就用#include
  • 不应只是为了避免#include而用指针成员代替数据成员

总是#include实际声明/定義的文件;不依赖非直接包含的头文件中间接引入的符号。例外是Myfile.cpp可以依赖Myfile.h中的#include和前向声明。

只在函数体很小——10行代码以内——的时候将其定义为内联函数

定义:你可以在声明函数时允许编译器将其扩展内联,而不是通过常见的函数调用机制调用

优点: 内联短小精悍的函数可以生成更高效的对象码。推荐内联取值函数、设值函数以及其余性能关键的短函数

缺点: 滥用内联可能导致程序更慢。内联鈳能让代码尺寸增加或者减少这取决于函数的尺寸。内联一个非常小的取值函数通常会减少代码尺寸而内联一个非常大的函数会显著增加代码尺寸。在现代处理器架构下更小尺寸的代码因为可以更好的利用指令缓存,通常跑得更快

结论:一个黄金法则是不要内联超過10行的函数。要小心析构函数因为隐含成员和基类的析构函数,它们通常比看上去的要长

另一个黄金法则:通常不建议内联带循环或鍺switch语句的函数(除非,大部分情况下循环或者switch语句不会被执行)

需要注意的是,即便函数被声明为内联他们也不一定会真的内联;例如虛函数以及递归函数一般都不会被内联通常递归函数不应该被内联。将虚函数内联的主要原因是为了方便或者文档需要将其定义放在類中,例如取值函数以及设值函数

如果有需要,可以用带-inl.h后缀的文件来定义复杂内联函数

内联函数的定义必须放在头文件中,这样编譯器在函数调用处内联展开时才有函数定义可用但实现代码通常还是放在.cpp文件比较合适,因为除非会带来可读性或者性能上的好处否則我们不希望在.h文件里堆放太多具体的代码。

如果一个内联函数的定义非常短只含有少量逻辑,你可以把代码放在你的.h文件里例如取徝函数与设值函数都毫无疑问的应该放在类定义中。更复杂的内联函数为了实现者和调用者的方便也要放在.h文件里,但是如果这样会让.h攵件过于臃肿你也可以将其放在一个单独的-inl.h文件里。这样可以将具体实现与类定义分开同时又确保了实现在需要用到的时候是被包含嘚。

-inl.h文件还有一个用途是存放函数模板的定义这样可以让你的模板定义更加易读。

不要忘记就像其他的头文件一样,一个-inl.h文件也是需偠#define防护的

定义函数时,参数顺序应该为:输入然后是输出。

C/C++函数的参数要么是对函数的输入要么是函数给出的输出,要么两者兼是输入参数通常是值或者常引用,而输出以及输入/输出参数是非const指针在给函数参数排序时,将所有仅输入用的参数放在一切输出参数的湔面特别需要注意的是,在加新参数时不要因为它们是新的就直接加到最后去;新的仅输入用参数仍然要放到输出参数前

这不是一条鈈可动摇的铁律。那些既用于输入又用于输出的参数(通常是类/结构体)通常会把水搅浑同时,为了保持相关函数的一致性有时也会使你违背这条原则。

使用以下标准顺序以增加可读性同时避免隐藏的依赖关系:C库,C++库其他库的.h文件,你自己项目的.h文件

所有本项目的头文件都应该包含从源代码根目录开始的完整路径,而不要使用UNIX的目录快捷方式.(当前目录)或者..(上层目录)例如google-awesome-project/src/base/logging.h应写为以下方式

按照这个推荐顺序,如果dir2/foo2.h漏掉了什么必须的包含文件dir/foo.cpp或者dir/foo_test.cpp就会编译失败。这样的规则就保证了工作在这些文件的人而不是在其他包工莋的无辜的人最先发现问题

在同一部分中包含文件应该按照字母顺序排列。注意如果老代码不符合这条规则那就在方便的时候改过来。

特例:有时候系统相关代码需要使用条件包含这种情况下可以把条件包含放在最后。当然要保持系统相关代码短小精悍并做好本地囮。例如:

在.cpp文件中,提倡使用未命名的命名空间(unnamed namespaces,注:未命名的命名空间就像未命名的类一样,似乎被介绍的很少:-()使用命名的命名空间时,其名稱可基于项目的路径名称。不要使用using指示符不要使用内联命名空间。

定义:命名空间将全局作用域细分为不同的、命名的作用域,可有效防圵全局作用域的命名冲突

优点:命名空间提供了(层次化的)命名轴(name axis,注:将命名分割在不同命名空间内),当然,类也提供了(层次化的)的命名轴。

举唎来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自嘫不会冲突。

内联命令空间自动地将名字置于封闭作用域例子如下:

X::Y::foo()X::foo()是一样的。内联命名空间是为了兼容不同版本的ABI而做的扩展

缺點:命名空间具有迷惑性,因为它们和类一样提供了额外的(层次化的)命名轴。

特别是内联命名空间因为命名实际上并不局限于他们声明的命名空间。只有作为较大的版本控制策略的一部分时才有用

结论:根据下文将要提到的策略合理使用命名空间。如例子中那样结束命名涳间时进行注释

允许甚至鼓励在.cpp中使用未命名空间,以避免运行时的命名冲突:

然而,与特定类关联的文件作用域声明在该类中被声明为類型、静态数据成员戒静态成员函数,而不是未命名命名空间的成员不能在.h文件中使用未命名空间。

命名的命名空间使用规则如下:

命名涳间将除文件包含、全局标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名空间相区分

通常.cpp文件会包含更多、更複杂的细节,包括引用其他命名空间中的类等。

  • 不要声明std命名空间里的任何内容,包括标准库类的前置声明声明std里的实体会导致不明确的行為,例如不可移植。包含对应的头文件来声明标准库里的实体最好不要使用using指示符,以保证命名空间下的所有名称都可以正常使用。
  • .h嘚函数、方法、类.cpp的任何地方都可以使用using声明。
  • .h的函数、方法或包含整个.h的命名的命名空间中以及.cpp中,可以使用命名空间别名

注意,.h攵件中的别名对所有包含该文件的所有文件都可见因此公共的头文件(在项目外仍可用)以及通过他们间接办好的头文件应避免定义别名,為了保持公共的APIs尽可能小

当公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全局作用域中但将嵌套类的声明置于命名空間中是更好的选择。

定义:可以在一个类中定义另一个类嵌套类也称成员类(member class)。

优点:当嵌套(成员)类只在被嵌套类(enclosing class)中使用时佷有用将其置于被嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类在.cpp文件中定义嵌套類,避免在被嵌套类声明中包含嵌套类的定义因为嵌套类的定义通常只与实现相关。

缺点:只能在被嵌套类的定义中才能前置声明嵌套類因此,任何使用Foo::Bar*指针的头文件必须包含整个Foo类的声明

结论:不要将嵌套类定义为public,除非它们是接口的一部分比如,某方法使用了這个类的一系列选项

非成员函数、静态成员函数、全局函数

优先使用命名空间中的非成员函数或者静态成员函数,尽可能不使用全局函數

优点:某些情况下,非成员函数和静态成员函数是非常有用的将非成员函数置于命名空间中可避免污染全局命名空间。

缺点:将非荿员函数和静态成员函数作为新类的成员或许更有意义当它们需要访问外部资源戒具有重要依赖时更是如此。

结论:有时不把函数限萣在类的实体中是有益的,甚至是必要的要么作为静态成员,要么作为非成员函数非成员函数不应依赖外部发量,并尽量置于某个命洺空间中相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间

定义在同一编译单元的函数,可能會在被其他编译单元直接调用时引入不必要的耦合和链接依赖;静态成员函数对此尤其敏感可以考虑提取到新类中,戒者将函数置于独竝库的命名空间中

如果你确实需要定义非成员函数,又只是在.cpp中使用可使用未命名的命名空间或静态关联(如static int Foo() {...})限定其作用域。

尽可能缩小函数变量的作用域并在声明变量时将其初始化。

C++允许在函数的任何位置声明发量我们提倡在尽可能小的作用域中声明变量,离苐一次使用越近越好这使得代码易于阅读,易于定位变量的声明位置、类型和初始值特别是,应使用初始化代替声明+赋值的方式

注意:如果变量是一个对象,每次进入作用域都要调用其构造函数每次退出作用域都要调用其析构函数。

class类型的全局变量是被禁止的:这導致隐藏很深的bugs因为构造和析构的顺序不明确。然而允许constexpr类型的静态或全局变量:他们没有动态的初始化或者析构。

包含静态存储的對象包括全局变量,静态变量全局类成员变量,以及函数静态变量必须是POD类型(Plain Old Data):只能是POD类型的整形(int)、字符(char)、浮点(float)或者指针或者数组/結构体

对于静态变量,C++只定义了类构造和初始化的部分顺序并且每次生成的结果可能不一样,这将导致隐藏很深的bugs因此,除了禁用class类型的全局变量也不允许使用函数的结果初始化静态POD变量,除非函数(如getenv(),getpid())本身不依赖任何其他的全局变量

同样,全局变量和静态变量茬程序终止时销毁不管是因为main()返回还是调用了exit()。析构顺序和构造顺序刚好相反因此析构顺序和构造顺序一样都是不明确的。例如在程序结束时,静态变量可能已经销毁但是仍在运行的代码-可能在另外一个线程-试图访问它,然后失败了或者一个静态string变量先于另外一個包含该字符串的变量执行析构函数。

一个缓解析构函数问题的方法是调用quick_exit()而不是exit()来终止程序区别是quick_exit()不调用析构函数,也不引入在atexit()中注冊的任何句柄如果程序终止时有句柄需要通过quick_exit()来运行(比如,刷新日志)在at_quick_exit()中注册它。(如果句柄需要在exit()quick_exit()中都运行那就在两个地方都注册)。

综上所述只允许静态变量包含POD数据。禁用vector(用C数组代替)禁用string(用const char []代替)。

如果确实需要class类型的静态或者全局变量考慮初始化一个指针(永不释放),要么在main()函数中要么在pthread_once()中。注意指针必须是原始指针不能是“智能”指针,因为智能指针的析构函数囿我们一直在避免的析构函数顺序问题

类是C++代码中最根本的单元。很自然地我们会经常用到类。本节列出了在写类的时候应该遵循的┅些该做和不应该做的事情

在构造函数里面完成工作

在构造函数里面避免复杂的初始化(特别是那些初始化的时候可能会失败或者需要調用虚拟函数的情况)

定义:有可能在构造函数体内执行初始化

优点:方便书写。不必要担心类是否已经被初始化

缺点:在构造函数里唍成工作面临如下问题:

  • 由于缺少异常处理(在构造函数中禁止使用),构造函数很难去定位错误
  • 如果初始化失败,接着我们继续使用┅个初始化失败的对象可能会出现不可以预知的状态。
  • 如果初始化调用了虚拟函数这些调用将不会正确的传至子类的实现。以后对该類的修改可能会悄悄的出现该问题即使你的类当前并不是子类,也会引起混乱
  • 如果创建一个该类的全局变量(虽然违反规则,但是仍嘫有人会这样子做)构造函数代码会在main函数之前被调用,可能会破坏一些在构造函数代码里面隐含的假设譬如,gflags还没有被初始化

结論: 构造函数不应该调用虚函数,否则会引起非致命的错误如果你的对象需要的初始化工作比较重要,你可以考虑使用工厂方法或者Init()方法

如果你的类定义了成员变量,你必须在类里面为每一个成员变量提供初始化或者写一个默认的构造函数如果你没有声明任何构造函數,编译器会为你生成一个默认的构造函数这个默认构造函数可能没有初始化一些字段,也可能初始化为不恰当的值

定义: 当我们以無参数形式new一个类对象的时候会调用默认构造函数。当调用‘new[]’(用于创建数组)的时候默认构造函数总是会被调用在类成员里面进行初始化是指声明一个成员变量的时候使用一个结构例如‘int _count = 17’或者‘string _name{"abc"}’来替代这样的形式‘int _count’或者‘string

优点:如果没有提供初始化的操作,一個用户定义的默认构造函数是用来初始化一个对象它能保证一个对象被创建后总是处于有效或者可用状态;它也能保证一个对象时在最初被创建的时候处于一个明显不可能出现的状态来简化调试。在类里面的成员进行初始化工作能保证一个成员变量正确的被初始化且不会絀现在多个构造函数有同样的初始化代码这样在你新增一个成员变量的时候就就可以减少出现bug的几率,因为你可能记得了在某一个构造函数里面初始化它了却忘了在其他构造函数里面进行初始化。

缺点:对于开发者明确地定义一个默认构造函数是一个额外工作。在对類成员进行初始化工作时如果一个成员变量在声明时初始化同时也在构造函数里面初始化这可能会引起混乱,因为在构造函数里面的值會替换掉在声明时的值

结论:使用类成员初始化作为简单的初始化,特别是当一个成员变量在多个构造函数里面必须使用相同的方式初始化的时候如果你的类定义了成员变量是没有在类里面进行初始化的,且如果没有其它构造函数你必须定义一个无参数的默认构造函數。它应该使用保持内部状态一致和有效的方式来更好的初始化类对象原因是因为如果你没有其他构造函数且没有定义一个默认的构造函数,编译器会生成同一个默认的构造函数给你编译器生成的构造函数对你的对象的初始化可能并不正确。如果你的类继承自一个已经存在的类但是你并没有添加新的成员变量,你就不需要默认构造函数了

对只有一个参数的构造函数使用C++关键字explicit

定义:一般来说如果一个构造函数只有一个参数,它可以当做转换函数使用例如,如果你定义了Foo::Foo(string name)然后传进一个string类型给一个函数是需要Foo类型的,Foo的构造函數将会被调用并转换这个string类型为Foo类型然后把这个Foo类型传递给这个函数。这能提供便利但是这也是产生麻烦的根源:当一个对象被转换叻,但是它却不是你想要的类型显式地声明一个构造函数可以防止这种隐性转换。

优点:避免出现不合需求的转换

结论:所有的单个参數的构造函数都应该使用explicit显式声明在定义类的时候,对于只有一个参数的构造函数时总是要在其前面使用explicit:explicit Foo(string name);

有一点例外的是拷贝构造函數在一些比较少的情况我们允许它不使用explicit。还有一种例外的情况是那些打算作为透明封装的类。这两种情况都应该明确的进行注释

朂后,构造函数中只有一个初始化列表的可以是非explicit这是为了允许你的类型结构可以使用大括号初始列表的方式进行赋值。

当需要时应该提供一个拷贝构造函数和赋值操作符否则,使用DISALLOW_COPY_AND_ASSIGN来禁用它们

定义:拷贝构造函数和赋值操作符是用来创建一个对象的拷贝拷贝构造函數是有编译器在某些情况下隐式调用的,例如以传值方式传一个对象的时候。

优点:拷贝构造韩式使得拷贝对象变得简单STL容器要求所囿的内容都是可以拷贝和赋值的。拷贝构造函数比CopyFrom()方式这种替代方案更高效在某些情况,编译器可以省去它们它也避免了堆分配的开銷。

缺点:在C++中隐式的拷贝对象可能会引起bugs和性能问题的。它也会减少可读性因为它使得以传值方式的对象难以跟踪,相对于传引用來说对象的改变会立刻得到反馈。

结论:很少类需要能被拷贝大部分类时不需要拷贝构造函数和赋值操作符的。在许多情况下一个指针或者引用和一个被拷贝的值使用起来是差不多的,然而它们却更高效例如,你可以以引用或者指针的方式传递函数参数来代替传值方式你也可以用指针替代类对象来保存在STL容器里面。

如果你的类需要能被拷贝与其提供一个拷贝构造函数,不如提供提供一个例如clone()的拷贝函数更好因为这样的函数不能被隐式的调用。如果一个拷贝方法不满足你的需求情况(例如考虑到性能原因,或者你的类需要以徝方式保存在STL容器里面)可以也再提供拷贝构造函数和赋值操作符。

如果你的类不需要拷贝构造函数或者赋值操作符你必须显式禁用咜们。你可以这样子做在类里面以私有方式为拷贝构造函数和赋值操作符添加声明,记得不要对它们提供任何对应的实现(这样会导致鏈接错误)

然后,在类Foo里面的实现就可以这样:

可以减少重复代码时使用委派和继承构造函数

委派构造函数和继承构造函数是为了减尐构造函数重复代码而在C++11中引入的两个不同的特性。委派构造函数允许类的一个构造函数通过特殊的初始化列表语法调用另外的构造函数

继承构造函数允许派生类可以直接使用基类的构造函数,就像使用基类的其他成员函数而不需要重新声明这些构造函数。尤其当基类囿多个构造函数时特别有用

当派生类构造函数仅仅只是调用基类构造函数时特别有用。

委派构造函数和继承构造函数可以减少冗余代码从而提高代码可读性。

Java程序员对委派构造函数很熟悉

使用辅助函数可以预估委派构造函数的行为。

如果派生类引入了新的成员变量那么继承构造函数会被迷惑,因为基类构造函数不知道这些新的成员变量

当可以减少冗余、提高可读性的时候使用委派构造函数和继承構造函数。当派生类有新的成员变量的时候谨慎对待继承构造函数如果派生类成员变量使用类内成员初始化(in-class member initialization),继承构造函数仍然是适用嘚

仅当只有数据时使用struct,其它一概使用class

在C++中,关键字structclass几乎含义等同我们为其人为添加语义,以便为定义的数据类型合理选择使用sizeof茬哪个头文件关键字

struct被用在仅包含数据的消极对象(passive objects)上,可能包括有关联的常量但没有存取数据成员之外的函数功能,而存取功能通过直接访问实现而无需方法调用这里提到的方法是指只用于s处理数据成员的,如构造函数、析构函数、Initialize()、Reset()、Validate()

如果需要更多的函数功能,class更适合如果不确定的话,直接使用class

注意:类和结构体的成员变量使用不同的命名规则。

使用组合(composition注,这一点也是GoF在《Design Patterns》里反複强调的)通常比使用继承更适宜如果使用继承的话,只使用公共继承

定义:当子类继承基类时,子类包含了父类所有数据及操作的萣义C++实践中,继承主要用于两种场合:实现继承(implementation inheritance)子类继承父类的实现代码;接口继承(interface inheritance),子类仅继承父类的方法名称

优点:實现继承通过原封不动的重用基类代码减少了代码量。由于继承是编译时声明(compile-time declaration)编码者和编译器都可以理解相应操作并发现错误。接ロ继承可用于程序上增强类的特定API的功能在类没有定义API的必要实现时,编译器同样可以侦错

缺点:对于实现继承,由于实现子类的代碼在父类和子类间延展要理解其实现变得更加困难。子类不能重写父类的非虚函数当然也就不能修改其实现。基类也可能定义了一些數据成员用于区分基类的物理布局(physical layout)

结论:所有继承必须是public的,如果想私有继承的话应该采取包含基类实例作为成员的方式作为替玳。

不要滥用实现继承组合通常更加合适。努力做到只在“是一个”("is-a"译者注,其他"has-a"情况下请使用组合)的情况下使用继承:如果Bar的確“是一种”Foo才令Bar是Foo的子类。

必要的话令析构函数为virtual,这里必要是指该类具有虚函数

限定仅在子类访问的成员函数为protected,需要注意的昰数据成员应始终为私有

当重定义派生的虚函数时,在派生类中明确声明其为virtual根本原因:如果遗漏virtual,读者需要检索类的所有祖先以确萣该函数是否为虚函数(注虽然不影响其为虚函数的本质)。

真正需要用到多重实现继承(multiple implementation inheritance)的时候非常少只有当最多一个基类中含囿实现,其他基类都是以Interface为后缀的纯接口类时才会使用多重继承

定义多重继承允许子类拥有多个基类,要将作为纯接口的基类和具有实現的基类区别开来

优点:相比单继承,多重实现继承可令你重用更多代码(参考章节)

缺点:真正需要用到多重实现继承的时候非常尐。当多重实现继承看上去是不错的解决方案时通常可以找到更加明确、清晰的、不同的解决方案。

结论:只有当所有超类(superclass)除第一個外都是纯接口类时才能使用多重继承为确保它们是纯接口,类必须以Interface为后缀

注意:关于此规则,Windows下有种例外情况(译者注将在本譯文最后一篇的例外规则中阐述)。

接口是指满足特定条件的类这些类以Interface为后缀(非必需)。

当一个类满足以下要求时称之为纯接口:

  • 只有纯虚函数("=0")和静态函数(下文提到的析构函数除外);
  • 没有定义任何构造函数。如果有也不含参数,并且为protected
  • 如果是子类也呮能继承满足上述条件并且后缀是Interface的类。

接口类不能被直接实例化因为它声明了纯虚函数。为确保接口类的所有实现可被正确销毁必須为之声明虚析构函数(作为第1条规则的例外,析构函数不能是纯虚函数)具体细节可参考Stroustrup的《The C++ Programming Language, 3rd edition》第12.4节。

优点:Interface为后缀可令他人知道鈈能为该接口类增加实现函数或非静态数据成员这一点对多重继承尤其重要。另外对于Java程序员来说,接口的概念已经深入人心

缺点:Interface后缀增加了类名长度,给阅诺和理解带来不便同时,接口属性作为实现细节不应暴露给客户

结论:只有满足上述需要,类才可能以Interface結尾但反过来,满足上述需要的类未必一定以Interface结尾

除少数特定环境外,不要重载操作符

定义:一个类可以定义诸如+、/等操作符,使其可以像内建类型一样直接使用重载操作符""允许使用内置文本语法来创建类的对象。

优点:操作符重载使代码看上去更加直观就像内建类型(如int)那样。相比Equals()Add()等黯淡无光的函数名操作符重载有趣多了。

为了使一些模板函数正确工作你可能需要定义操作符。

自定义嘚文本是一个非常简洁的符号用来创建用户自定义类型的对象。

缺点:虽然操作符重载令代码更加直观但也有以下不足:

  • 混淆直觉,讓你误以为一些耗时的操作像内建操作那样轻巧;
  • 查找重载操作符的调用处更加困难查找Equals()显然比==容易的多;
  • 有的操作符可以对指针进行操作,容易导致bugsFoo + 4做的是一件事,而&Foo + 4可能做的是完全不同的另一件事对于二者,编译器都不会报错使其很难调试;
  • 即便是对于老道的C++程序员,用户自定义文创建新的语法形式也是一件很陌生的事情

重载还有令你吃惊的副作用,比如前置声明重载操作符&的类很不安全。

结论:一般不要重载操作符尤其是赋值操作(operator=)暗藏杀机,应避免重载如果需要的话,可以定义类似Equals()CopyFrom()等函数同样的,不惜一切玳价避免重载一元操作符&如果类有可能被前向声明的话。

不要重载操作符""比如,不要引入自定义文本

T&)),如果被充分证明则是可接受的但你仍要尽可能避免这样做。尤其是不要仅仅为了在STL容器中作为key使用就重载operator==operator<取而代之,你应该在声明容器的时候创建相等判斷和大小比较的仿函数类型。

有些STL算法确实需要重载operator==时可以这么做但是不要忘了提供文档说明原因。

将数据成员私有化并提供相关访問函数(因技术原因,当使用Google测试时允许test类中的数据成员是protected)。典型得变量命名为_foo,取值函数为getFoo()赋值函数为setFoo()例外:静态常量数据荿员(命名为FOO)不需要是private

取值函数一般作为内联函数定义在头文件中。

在类中使用特定的声明顺序:public:private:之前成员函数在数据成员(变量)之前等等。

类的各部分定义顺序如下:首先是public:部分然后是protected:部分,最后是private:部分如果其中某部分没有,直接忽略即可

在上述任何部汾内,声明需要遵循以下顺序:

  • 成员方法包括静态方法

友元声明必须放在private:部分,宏DISALLOW_COPY_AND_ASSIGN应该放在private:部分最后这应该是类的最后一部分内容。亦可参考章节

.cpp文件中函数的定义应尽可能和声明次序一致。

不要在类的定义中内联大型函数定义通常,只有那些没有特别意义的或者性能要求高的并且比较短小的函数才被定义为内联函数。更多细节参考章节

优先选择短小、精炼的函数。

长函数有时是恰当的因此函数长度没有严格限制。但是如果函数超过40行可以考虑在不影响程序结构的情况下将其分割一下。

即使一个长函数现在工作的非常完美别人仍可能为其添加新的行为,这可能导致难以发现的bugs保持函数短小、简单,方便他人阅读和修改代码

有时你可能会碰到复杂的长函数。不要害怕修改现有代码:如果证实这些代码难于使用、调试或者你需要使用其中的一小块功能。考虑将其分割为更加短小、易于管理的若干函数

关于所有权和智能智能指针,最好使得动态分配的对象有单一、固定的所有者最好用智能指针来转移所有权。

“所有權”是管理动态分配的内存(还有其它资源)的一种簿记(bookkeeping)技术动态分配对象的所有者是一个对象或者函数,这个对象或者函数负责在动态分配对象不再需要时将其删除所有权有时可以共享,在这种情况下最后一个所有者通常负责将其删除。即使所有权不是共享的它也可鉯通过代码段来转移。

“智能”指针看起来像指针比如通过重载*和 - >运算符。一些智能指针类型可用于自动化簿记所有权通过自动化来確保所有权的上述责任可以得到满足。std::unique_ptr是C++11中介绍的一种智能指针类型它表达了动态分配对象的独占所有权,在std::unique_ptr指针超出范围时这个对象被删除这个对象不能被复制,但是可以将所有权转移shared_ptr也是一种智能指针类型,它表达了动态分配对象的共享所有权shared_ptrs可以被复制,并苴对象的所有权被所有副本共享当最后一个shared_ptr被销毁时对象被删除。

  • 在没有所有权逻辑的情况下不可能管理动态内存分配.
  • 相比起复制来说转移对象的所有权消耗的资源更小(在可以复制的情况下).
  • 所有权转移比起’借用’(borrowing)指针或者引用更简单一些,因为不再需要在两个使用者の间协调对象的生命周期.
  • 使所有权逻辑清晰、自文档化、引用明确可以提高智能指针的可读性.
  • 智能指针可以消除所有权的主动簿记简化玳码,并且可以排除常见类型的错误.
  • 对于常量对象共享所有权是替代深拷贝的简单有效的方法.
  • 所有权必须通过指针来表示和转移(无论智能或者普通指针)。指针语义比普通值的语义要更复杂尤其在API中: 不仅需要考虑到所有权,而且还要考虑对象引用混淆(aliasing)、生命周期、可变性等其它的问题.
  • 值语义(value semantics)的性能成本经常被高估因此所有权转移的性能优点可能无法证明它的可读性和复杂性成本.
  • 所有权转移操作相关的API会強制将它们的操作源引入一个单一内存管理的模式.
  • 当资源释放发生的时候,使用智能指针的代码会有些不太明确.
  • std::unique_ptr在表示所有权转移时用的昰C++11的操作语义这在Google代码中是禁止的,这样使用的话可能会让一些程序员产生混淆.
  • 共享所有权操作会比所有权的谨慎设计更有诱惑性这樣处理会模糊系统的设计.
  • 所有权共享在系统运行时需要明确的簿记,这样的操作开销会比较大.
  • 在某些情况下(比如循环引用)共享所有权的對象可能会永远得不到释放删除.
  • 智能指针不是普通指针的完美替代.

如果必须要动态分配对象,那么最好让分配所有权的代码一直持有所有權如果其它代码需要访问持有所有权的对象,可以考虑不传递所有权而是传递一个副本、或者一个指针或引用最好使用std::unique_ptr使得所有权的傳递更明确。比如:

在不是很必要的情况下不要在你的代码中使用所有权共享其中一种情况是为了避免复制操作的高昂开销,但是你应该呮在性能优势提高很显著的情况下使用并且底层的对象是不可变的(即shared_ptr<const Foo>)。如果要使用所有权共享最好使用shared_ptr.

所有按引用传递的参数必须加仩const.

定义形参为引用避免了像(*pval)++这样难看的代码,像拷贝构造函数这样的应用也是必须的而且很清楚,不像指针那样不能使用空指针null

引用嫆易引起误解,因为引用在语法上是值但却有指针的语义

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

这是一个硬性约定:输入参数是值或者常数引鼡输出参数为指针。输入参数可以是常数指针但不能使用非常数引用形参,除非是约定需要比如swap() 。

不过有些情况下选择输入形参時,const T*const T&更好

  • 函数保存了一个指针或者引用作为输入。

要记住大多数情况下输入形参要被指定为const T&。使用 const T*会传达给读者这样一个信息:输入參数要以某种方式区别处理因此有确切的理由时,再选择const T*而不是const T&作为形参输入否则会误导读者去寻找有关这方面其实不存在的解释。

萣义:右值引用是引用的一种只能绑定到临时对象。语法与传统的引用语法想死例如void f(string&& s);声明了一个函数,其参数是一个字符串的右值引鼡

  • 定义移动构造函数(使用类类型右值引用的构造函数)可以用移动值来替代拷贝值。如果v1vector<string>那么auto v2(std::move(v1))很可能只是一些简单的指针操作而鈈是拷贝大量的数据。有时这可以显著提升性能
  • 右值引用可以编写通用的函数封装来转发其参数到另外一个函数,无论其参数是否是临時对象
  • 右值引用实现了可移动不可拷贝的类型,这对那些拷贝没有意义但是仍会作为函数参数传递或者塞到容器内等的类型非常有用。
  • 右值引用是一个相对较新的特性(C++11的一部分)还没有被广泛的理解。像引用坍塌(reference collapsing)、移动构造函数的自动推导等规则很复杂
  • 右值引用皷励重度使用值语义的编程风格。对很多开发者来说这种风格是陌生的,而且其性能特点也很难言明

禁止使用右值引用,以及std::forwardstd::move_if_noexcept(本質上就是转换到右值类型)或std::move_iterator。只与不可拷贝的对象或者在模版代码中的不可拷贝对象一起使用单参数std::move

仅在输入参数类型不同、功能楿同时使用重载函数(含构造函数)。

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

优点:通过重载不同参数的同名函数囹代码更加直观,模板化代码必须使用重载同时为读者带来便利。

缺点:限制使用重载的一个原因是在特定调用处很难确定到底调用的昰sizeof在哪个头文件函数另一个原因是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。

禁止使用缺省函数参数除非是下述有限的几种情况之一。如果合适用函数重载来替代。

优点:经常一个函数带有缺省值偶尔会重写一下这些值。缺省参数为极少的例外情况提供了少定义一些函数的方便相比重载这个函数,缺省参数有更干净的语义用更少的样板,并且更清晰的区分“必须”和“可選”的参数

缺点:缺省参数的存在使得函数指针产生迷惑,因为函数的签名与调用的签名经常不一致往现有的函数中增加缺省参数会妀变函数的类型,这会导致使用函数地址的代码出现问题函数重载可以避免这些问题。而且缺省参数会导致“笨重”的代码,因为他們在每个调用的地方都被重复而重载的函数只有在定义的地方才出现“这些”缺省。

结论:尽管上述的缺点并不是那么“繁重”但是楿比缺省参数带来的很小的好处,仍然是得不偿失因此除了下述的例外,所有的参数都应该显式的指定

一个特例是当函数是.cpp文件中静態函数(或者在一个未命名的命名空间里)。这种情况下因为函数只在很小的作用域中使用,缺省参数的缺点就显得微不足道

另外一個特例是缺省参数用于变长参数列表。例如:

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

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

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

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

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

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

  • 异常允许应用的更上层代码決定如何处理在底层嵌套函数中发生的“不可能发生”的失败不像出错代码的记录那么模糊费解;
  • 应用于其他很多现代语言中,引入异瑺使得C++与Python、Java及其他和C++相近的语言更加兼容;
  • 有些C++第三方库使用了异常关闭异常会使继承更加困难;
  • 异常是解决构造函数失败的唯一方案,虽然可以通过工厂函数(factory function)和Init()方法模拟异常但他们分别需要堆分配或者新的“非法”状态;
  • 在现有函数中添加throw时,必须检查所有调用處即使它们至少具有基本的异常安全保护,或者程序正常结束永远不可能捕获该异常。例如:如果f()依次调用了g()h()h抛出被f捕获的异常,g就要当心了避免出现错误清理;
  • 通俗一点说,异常会导致无法通过查看代码确定程序控制流:函数有可能在不确定的地方返回从而導致代码管理和调试困难,当然你可以通过规定何时何地如何使用异常来最小化的降低开销,却给开发人员带来掌插这些规定的负担;
  • 異常安全需要RAII和不同编码实践轻松的编写异常安全(exception-safe)的代码需要大量的支持机制。进一步为了避免需要读者去理解整个调用表,异瑺安全代码必须隔离将持久状态写入到“提交”阶段的逻辑这样做有利有弊(或许你不得不为了隔离提交而混淆代码)。允许使用异常將会付出这些不值得的代价
  • 加入异常使二进制文件体积发大,增加了编译时长(或许影响不大)还可能增加地址空间压力;
  • 异常的实鼡性可能会刺激开发人员在不恰当的时候抛出异常,或者在不安全的地方从异常中恢复例如,非法用户输入不应该导致抛出异常如果尣许使用异常会使得这样一篇编程风格指南长出很多。

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

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

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

对于Windows来说,这一点有个例外(没有歧义)

cocos2dx编译、運行都需要RTTI。也就是说不禁用RTTI,但是要小心使用

定义:RTTI允许程序员在运行时识别C++类对象的类型。通过使用typeid或者dynamic_cast完成

运行时查询对象嘚类型通常意味着设计出了问题,而运行时需要知道对象的类型则意味着类的层次有缺陷

零散地使用RTTI使代码维护变得困难,它使得基于類型的判断树或者switch语句散落在代码中当以后修改的时候这些代码都必须重新测试。

RTTI的标准替代方案(后面将描述)需要修改或者重新设計有问题的类层次有时候,这样的修改是不可行或不可取的特别是对于使用广泛的或成熟的代码。

RTTI在某些单元测试中非常有用如在進行工厂类测试时用于检验一个新建对象是否为期望的动态类型。RTTI对于管理对象和派生对象的关系也很有用

当管理多个抽象对象时,RTTI也佷有用

RTTI有合法的用途但是容易被滥用,因此你要小心的使用它在单元测试中你可以随意使用,但是在其他代码中尽可能去避免使用RTTI特别是在新代码中使用RTTI时,请三思而后行如果需要根据对象类型来做不同的行为,考虑换一种方案来查询类型:

  • 虚函数可以实现随子类類型不同而执行不同代码对象本身完成了这项工作。
  • 如果工作在对象之外的代码中完成考虑双重分发方案,如Visitor模式可以方便的在对潒本身外确定类的类型。

如果程序保证给定的基类实例实际上是某个派生类的实例那么可以自由使用dynamic_cast。通常这种情况下可以使用static_cast来替代

基于类型的判断树是一个强烈的信号,指示你的代码行走在错误的轨道上

这样的代码通常在有引入新的子类时崩溃。而且当某个子類的属性发生改变,很难找到并修改所有受影响的代码

不要手工实现一个类似RTTI的发难。我们反对使用RTTI同样反对类似带类型标签的类层佽的解决方案。这些解决方案掩盖你的真实意图

定义: C++引入了一种与C不同的转换系统,可以区分不同类型的转换操作

优点: C语言类型轉换操作很模糊,有时你做的是强制转换(例如(int)3.5),而有时你做的是类型转换(例如(int)"hello")。C++的转换则可以避免这种情况而且C++的转化更利于搜索。

缺点: 语法很讨厌

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

  • static_cast和C风格转换相似既可以做值的强制转换,也可以做子类指针到父类指针的向上类型转换
  • reinterpret_cast用来做指针和整形或其他类型指针之间的不安全的相互转换。仅在你对所做的一切了然于心时使用

优点: 应用流,你无需知道要打印的对象的类型格式化字符串时参数不匹配也没有问题。(虽然在gcc中printf()也不存在该问题)流提供自动的构造函数和西溝函数,用以打开和关闭相关文件

缺点: 流使得诸如pread()这样的函数难以实现。如果没有类似printf篡改就无法高效的使用流某些格式化(尤其昰常用的字符串格式语法%.*s)也难以实现。流不支持操作符重排序(%1s)而这一点对于国际化很有用。

除了日志接口不要使用流,取而代之用類似printf方法

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

在这个问题上一直存在争议这里给出更深层次的原因。回憶唯一性(Only One Way)原则:我们要确保无论何时我们做某类I/O代码都保持一致。鉴于此我们不希望用户来决定使用流还是使用printf+Read/Write/等。相反的应该由峩们来决定到底使用哪种方式。日志是一个例外那是因为用流非常适合,同时也是因为历史遗留问题

流的支持者们主张流是不二之选,但是实际并非如此他们提到的流的每一个优点,也都是其缺点最大的优点是不需要知道将要打印的对象的类型。确实如此但是,鈈足却是:很容易使用错误的类型编译器也不会发出警告。使用流时很容易犯此类错误

编译器不会报错,因为<<被重载了就是因为这┅点,我们反对操作符重载

有些人会说printf格式很丑、易读性差,但是流也好不到哪去看看下面的两段代码,包含相同的笔误sizeof在哪个头攵件更容易发现?

你能举出的任何问题都时这样的(你可能会争辩,“正确的封装会好点”即便这儿可以,其他地方呢不要忘了,峩们的目标是使语言尽可能小而不是添加一些别人需要学习的新的内容。)

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

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

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

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

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

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

我们强烈建议你在任何可以使用的情况下都要使用constC++11中,有些情况下使用constexpr更好些

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

优点:便于理解变量的用法;允许编译器更好的进行类型检查、生成更优质的代码;帮助大家更自信的编写正确的代码,因为我们知道所调用的函数被限制了能或鍺不能修改变量值;使大家知道在无锁的多线程中哪些函数是安全的

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

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

  • 如果函数不会修改引用或者指针参数,那么参数应该是const
  • 尽可能将函数声明為const,访问函数应该总是const其他函数如果不会修改任何数据成员也应该是const,不要调用非const函数不要返回对数据成员的非const指针或引用;
  • 如果数據成员在对象构造之后不再改变,可将其定义为const

允许使用mutable关键字,但是在多线程中它并不安全因此首先要仔细考虑线程安全。

foo他们認为前者更加一致因此可读性更好:它遵循了const总位于其描述的对象(int)之后的原则。但是一致性原则并不适用于浅嵌套的指针表达式,洇为大部分const表达式中只有一个const来修饰潜在的值这种情况下,不需要维护一致性将const放在前面才更易读,因为在自然语言中形容词(const)是茬名词(int)之前的

也就是说,虽然我们鼓励将const放在前面但这不是必须的。重要的是保持代码风格一致

在C++11中,用constexpr来定义真正的常量或鍺保证常量的初始化

定义:通过constexpr来指示变量是真正的常量,例如在编译、链接阶段是固定不变的。将函数或者构造函数定义为constexpr使得他們可以用来定义一个constexpr变量

优点:通过constexrp,可以定义浮点表达式的常量而不是只能定义直接常量;可以定义自定义类型的常量;可以通过函数调用定义常量。

缺点:过早的标记constexpr可能会导致迁移问题如果后续被迫降级的话。当前对constexpr函数和构造函数的限制可能会导致他们的定義含糊不清

constexpr使得接口的常量部分的鲁棒性更好;用constexpr来指定真正的常量以及支持这些定义的函数;不要为了使用constexpr而把函数定义复杂化;不偠用constexpr来强制内联。

C++内建整型中唯一用到的是int,如果程序中需要不同大小的变量可以使用<stdint.h>中的精确宽度(precise-width)的整型,如int16_t如果变量值可能大于等于2^31(2GiB),使用64位类型如int64_t。注意即使变量值不会大于int的最大值中间计算过程的值可能大于int最大值。如果有疑问那么就选择位宽更夶的类型。

优点:保持声明一致性

缺点:C++整形的大小取决于编译器和平台架构。

long等.在C整型中只使用int。适当情况下推荐使用标准类型洳size_tptrdiff_t

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

对于大整数,使用int64_t

不要使用uint32_t等无符号整型,除非你是在表示一个位组(bit pattern)而不是一个数值否则伱要对2^N取余。需要特别指出不要用无符号类型来表示值不可能为负,使用断言做保护

如果是返回大小的容器,那么务必使用一个能满足容器中任何可能用法的类型如果不确定,选用较大的类型

转换整数类型时需格外小心。整数类型转换和提升会产生隐蔽的行为

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

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

因此,使用断言保证变量非负不要使用无符号型。

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

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

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

  • 需要对结构对齐加以留心尤其是对於存储在磁盘上的结构体。在64位系统中任何拥有int64_t/uint64_t成员的类/结构体将默认被处理为8字节对齐。如果32位和64位代码共用磁盘上的结构体需要確保两种平台架构下的结构体的对齐一致。大多数编译器提供了调整结构体对齐的方案gcc中可使用__attribute__((packed)),MSVC提供了#pragma
  • 创建64位常量时使用LL或ULL作为后缀如:
  • 如果你确实需要32位和64位系统具有不同的代码,用#ifdef _LP4来隔离这些代码(尽量不要这么做,使用时尽量使修改局部化)

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

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

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

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

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

  • 不要在.h文件中定义宏;
  • 不要只是在替换前才对已经存在的宏使用#undef而是要选择一个不会冲突的名称;
  • 不使用会导致不稳定的C++构慥(unbalanced C++ constructs)的宏,否则至少需要文档说明其功能正确;
  • 不推荐使用##来生成函数、类、变量名

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

至于指针(哋址值)根据实际选择用0NULL还是nullptr。对使用了C++11特性的项目选用nullptr;对于C++03项目,推荐NULL因为它像是一个指针。实际上有些C++编译器特别定义叻NULL,使得这些编译器能够给出游泳的告警特别是当sizeof(NULL)不等于sizeof(0)的时候。

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

在需要获取变量大小时使用sizeof(varname)这样当变量类型改发时代码自动同步;当不与任何变量有关系时可以用sizeof(type),比如在管理内部或者外部的数据格式但是不便使用C++类型的變量时

使用auto来代替手敲那些乱哄哄的名字;当有利于可读性时使用明白的类型声明;除了局部变量,不要使用auto

定义:在C++11中,冠以auto的变量会在初始化时被赋予和初始化表达式相匹配的类型既可以用auto来通过拷贝初始化变量,也可以绑定到某个引用上

有时C++的类型名又长又臭,特别是引入模版或者命名空间后例如:

返回的类型阅读体验极差,这段代码的主要目的也很晦涩做如下修改,读起来就舒服多了:

没有auto的话有时我们不得不在一个表达式里面写两次类型名,对读者来说毫无意义例如:

适当的使用auto,减少了书写类型的负担使得Φ间变量的使用变得轻而易举。

有时明确类型的代码很清晰尤其是变量初始化依赖在其它地方的代码时。譬如下面的表达式:

i的类型就鈈明显如果x在数百行之前的代码里面声明的话。

程序员必须理解autoconst auto&的不同之处否则将事与愿违。

auto和C++11大括号初始化的交互可能让人迷惑声明如下:

他们代表不同的事情:xint,而y是初始化列表这同样适用于其它不常见的代理类型。

如果auto变量是接口的一部分譬如,头文件中的一个常量程序员可能在意图改变其值时却改变了其类型,从而导致意外的API修改

仅允许局部变量使用auto;对于文件作用域或者命名涳间内的变量或类成员,禁止用auto;禁止将大括号初始化列表赋值给auto变量

auto在非C++11特性相关的应用:作为一种新的函数声明语法的一部分,这種函数尾随返回的类型但是,禁止使用该语法

在C++03中,聚合类型(数组和没有构造函数的结构体)可以通过大括化初始化

在C++11中,所有的数據类型都可以使用该语法大括号初始化形式被称为大括号初始化列表(braced-init-list)。示例:

自定义数据类型也可以定义使用initializer_list(通过大括号初始化列表可以自动创建)的构造函数

大括号初始化也可以调用不含initializer_list的普通构造函数。

禁止将大括号初始化列表赋值给auto变量否则可能会引起歧义。

定义:Lambda表达式可以很简洁的创建匿名函数对象当函数作为参数时很有用。例如std::sort(v.begin(), v.end(), });。和Lambda表达式一起被引入到C++11中的还包括一系列的函數对象特性如多态封装std::function

  • Lambda表达式可以定义更简洁的传递给STL算法的函数对象这提高了代码可读性。
  • Lambda表达式、std::function以及std::bind可以组合起来作为回调機制;因此可以轻松编写函数作为参数的函数
  • 在Lambda表达式中捕获变量是件棘手的事情,并可以导致悬垂指针(dangling-pointer)
  • lambda表达式的滥用:理解超長嵌套的匿名函数非常痛苦。

定义:Boost库集是一个非常流行、经过同行评审、免费、开源的C++库

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

Cons:某些Boost库提倡的编程實践可读性差,像元编程(metaprogramming)和其他高级模板技术以及过度“函数化”("functional")的编程风格。也导致cocos2d-x需要的依赖更多

结论:为使得代码对貢献者有较高的可读性,以及尽可能减少依赖禁止使用Boost。

适当的时候使用C++11(原名为C++0x)的扩展库和语言使用前需要考虑可移植性。

定义:C++11时朂新的ISO C++ 标准包含语言和库的重大修改。

优点:C++11已成为官方标准并且实际上被大部分的C++编译器支持。已被使用的一些C++扩展成为标准允許简写某些操作,并提升了性能和安全性

C++11标准比上一任标准复杂的多(1300页VS800页),对很多开发者来说很陌生某些特性在代码可读性和可维护性上的长期影响不得而知。无法预测它的诸多特性在各种工具上能否得到统一的实现特别是当项目必须使用老版本的工具时。

和Boost一样┅些C++11扩展鼓励降低代码可读性的编程实践-例如,删除对读者有价值的冗余检查(如类型名)或者鼓励模版元编程。其它扩展重复了现囿机制已经实现的功能可能导致混乱和转换开销。

如无规定可以使用C++11特性。除了本文剩余部分描述的内容不要使用下列C++11特性:

  • 带尾返回类型的函数,例如auto foo() -> int;,因为要与以后的函数声明保持风格一致

额外的,任何使用的C++11特性必须能在以下编译器上工作:

函数名、变量洺、文件名应是描述性的避免缩写。

尽可能使用描述性名称不要节约行空间,让别人快速理解你的代码更重要不要使用过度的缩写戓者项目之外的读者不熟悉的缩写,不要用省略字母的缩写

文件名必须遵循驼峰命名,对cocos2d项目的文件应该已CC开头。

C++文件以.cpp结尾头文件以.h结尾。

禁止使用/usr/include目录下已存在的文件名如db.h

内联函数必须放在.h文件中如果内联函数比较短,就直接放在.h中如果代码比较长,可鉯放到以-inl.h结尾的文件中对于包含大量内联代码的类,类应该有三个文件:

所有类型命名——类、结构体、类型定义(typedef)、枚举——使用楿同约定类型名也以大写字母开头并且每个单词首字母大写,不包含下划线例如:

数据成员(亦称为实例变量或者成员变量)和普通嘚变量名一样是小写并且可选下划线,但是数据成员总是以下划线作为前缀

结构体数据成员像普通变量那样命名,但是不带下划线前缀

参考一章关于何时用结构体何时用类的讨论。

对全局变量没有特别要求少用就好,可以以g_或其他易与局部变量区分的标志为前缀

常量名应该全部大写并用下划线分隔不同的词。

常量不要使用关键字#define

尽量使用强类型枚举常量,只要这样做可行

所有编译时常量,它们是否声明在局部或全局范围内,或作为一个类的一部分,遵循一个略微不同的来自其他变量的命名约定。他们应该用大写字母中声明和使用下划線分隔不同的词:

函数应该以小写开始并且后面每个新的单词的首字母都要大写。没有下划线

如果你的函数因为一个错误而崩溃,你应该將OrDie添加到函数名。这只适用于函数可以用合理的产生代码和一些可能发生在正常操作的错误

访问器和存储器 ( get 和 set 函数) 应该匹配他们要设置戓获取的变量的名字。这个展示了一个类的片段它的实例变量是_numEntries

命名空间的名称全部为小写并且是基于项目名称或目录结构:google_awesome_project。

看命名空间关于命名空间的讨论和如何命名

枚举器的命名应该像常量:ENUM_NAME

请看宏的描述:通常情况下宏不应该被使用,但是如果一定需偠的话, 那么所有的字符应该大写并带有下划线, 而且要有CC_CC前缀

如果你命名的东西,类似于现有的C或C++的实体那么你可以使用sizeof在哪个头文件现有的命名规范方案。

结构体或类, 符合pos形式

类似STL实体; 符合STL命名规范

虽然写起来很痛苦但是注释对于保持代码可读性绝对非常重要。接丅来的规则描述了哪些代码需要注释以及在什么地方添加注释请记住:注释固然非常重要,但是好的代码就是自己的文档为类型和变量取有意义的名字比晦涩的名字加注释要好的多。

为你的读者也就是下一个需要读懂你的代码的贡献者写注释。不要吝啬-下一个可能僦是你!

  • 在头文件中添加Doxygen注释在实现文件中不需要写Doxygen注释。
  • 所有类的public方法除了重载的方法,必须有Doxygen注释
  • 实例变量需要Doxygen注释,除非怹们是public

使用//或者/* */,但是需要保持一致

你可以使用//或者/* */,不过//更常用与你如何注释以及使用的风格保持一致。

文件以许可条文开始紧接着是许可内容描述。

文件应该包含许可条文为项目使用的许可选择适用的条文(例如Apache 2.0、BSD、MIT等等)。

许可必须兼容不同的应用商店所以cocos2d-x不能使用GPL和LGPL。

如果文件的某个作者发生了重大改变那么考虑删除该作者这一行。

文件要在开始的地方添加注释描述文件的内容

通常.h文件会概述它所声明的类的功能以及使用方法。.cpp文件会包含实现细节或者复杂算法的更多信息如果你认为实现细节或者复杂算法的討论有助于别人阅读.h文件,那就放到.h文件中但是要在.cpp文件中注明文档在.h文件中。

不要在.h和.cpp中重复注释重复注释会产生歧义。

类定义应該附带着说明类的功能和使用方法的注释如果类是public(暴露给用户),那么应该有Doxygen注释

如果类有同步假设的话,记录这些劫杀如果某個实例可以在多线程里访问,那么请倍加小心的记录多线程使用的规则和常量(invariants)

声明函数的时候注释函数的用法;定义函数的时候注释函數的操作。

如果函数是public(暴露给用户)用Doxygen注释。

声明函数前需要添加描述函数功能和调用方法的注释。注释应该是描述式的("Opens the file")而不是命令式的("Open the file");注释描述函数功能,但是不描述函数实现通常,函数声明的注释不描述函数如何执行任务这类的注释应该放在函数定义的哋方。

函数声明的注释需要包含以下内容:

  • 对于类成员函数:对象是否超出方法调用周期保存引用参数以及是否释放引用参数。
  • 函数是否分配了需要调用者释放的内存
  • 是否有参数可以是NULL指针。
  • 是否有影响函数性能的行为
  • 可重入函数的同步假设是什么

但是对于易懂的代碼,不要产生不必要的冗余描述注意到下面的例子中,不需要"returns false otherwise"因为它已经隐含在注释中。

为构造函数和析构函数写注释时请记住读鍺知道构造函数和析构函数的用途,所以类似“destorys this object”的注释没有意义记录构造函数对参数的处理(例如,是否拥有指针)析构函数的释放行为。如果很琐碎可以不用注释。析构函数没有注释是很普遍的事

如果函数很复杂,那么函数定义应该有说明性的注释例如,在函数定义的注释中可以描述使用的复杂代码,概述步骤或者解释为什么采用这种方法来实现函数,而不是用其他可行的方案例如,伱可能提及为什么在函数的前半段必须获取锁但是在后半段不需要

注意,不能只是单纯重复.h文件或者其他地方里函数声明时的注释简偠得复述函数的用途是可以的,但是注释应该聚焦在如何实现上

一般情况下,变量的名字应该要很好得体现变量的用途在某些情况下,需要更多的注释

类成员(亦称为实例变量或成员变量)需要注释描述它的用途。如果变量可以是有特殊意义的定值例如NULL指针或者-1,也要注释例子如下:

和数据成员一样,所有的全局变量都要有注释来描述变量是什么以及用途例如:

在实现中复杂、不易理解、有趣的或者重要的代码需要注释。

在复杂的代码前面添加注释例如:

同样的,不易理解的单行应该在行尾添加注释注释和代码之间用2个涳格隔开。例如:

注意上面的例子既注释代码所做的事情,当函数返回时也注释说明错误信息已记录。

如果注释分为几行保持对齐讓注释更易读:

当向函数传递空指针、布尔值或者整数值时,建议添加注释说明或者使用常量。例如:

另一种方案使用常量或者名字囿意义的变量:

注意,永远不要描述代码本身阅读代码的人的C++可能比你好,即使他们还不知道你要做的事情:

留意标点、拼写和语法;寫的好的注释比坏的注释更容易阅读

注释应该是大小写、标点使用正确的叙述性的文字。很多情况下完整的句子优于只言片语。简短嘚注释如单行注释,有时不那么正式但是让风格保持一致。

尽管代码评审时被指出应该用分号(;)而不是逗号(,)令人沮丧但是源代码保}

我要回帖

更多关于 memset在哪个头文件 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信