google c++代码规范-中文翻译

本文详细介绍了C++编程中关于头文件保护、依赖管理、内联函数定义、命名空间使用、类设计等方面的最佳实践,帮助开发者编写高质量、可维护的代码。

头文件< Header Files>

The #define Guard

所有的头文件都应该使用#define等来防止头文件重复包含,宏的格式应该为:<PROJECT>_<PATH>_<FILE>_H_

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

 

...

 

#endif // FOO_BAR_BAZ_H_

头文件依赖

当前面的声明已经满足依赖的时候,不要再使用#include

 

当你包含一个头文件的时候,你就引进了一种依赖,这种依赖会导致:一旦被包含的头文件发生改变,你的代码将会被重新编译。如果你的头文件还包含了其他的头文件,那么这些头文件的任何改变都会导致包含了你的头文件的代码被重新编译。因此,我们应该最小化包含头文件,特别要注意那些包含了其他头文件的头文件。

通过使用前向声明可以显著地减少需要包含的头文件的数量。例如,如果你的头文件想使用File类而不要进入声明了File类的文件,这时你的头文件就可以前向声明File类,而替代掉#include "file/base/file.h"

如何能在头文件中使用File类而不用进入它的定义文件?

l  我们可以声明Foo *或者Foo &类型的数据成员。

l  我们可以声明但并不定义带参和(或)返回Foo类型值得函数。(一个例外是,如果参数Foo或const Foo&拥有非显式的单参数构造,那么这种情况下就需要全面的定义以支持自动类型转换)。

l  我们可以定义Foo类型的静态数据成员。这是因为静态数据成员是被定义在类定义体之外的。

 

另一方面,如果你的类是Foo的子类或者有Foo类型的数据成员,那么你就必须包含Foo的头文件。

 

这样似乎我们应该使用指针(或者更好的是scoped_ptr,这是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放)。但无论如何,这样做降低了代码的易读性和程序的运行效率,所以如果目的仅仅是减少头文件包含,那么还是应该避免这种方法的使用。

 

当然,.cc文件一般都需要它们所使用到的类的定义,并且通常它们都需要包含多个头文件。

 

注意:如果你在源文件中使用了Foo,那么无论是通过使用#include还是通过使用前向声明,你自己就应该引进Foo的定义。不要依赖于通过头文件传递而不是直接被包含的符号。例外的是,如果在myfile.cc中使用了Foo,那么可以替代在myfile.cc中,转而在myfile.h中进行包含(#include)或者前向声明Foo。

内联函数

只有当函数短小,并且少于或等于10行的时候才能定义它们为内联函数。

定义(Definition:

优点(Pros:

缺点(Cons:

结论(Decision:

 

The -inl.h Files

你也许会需要使用带有-inl.h后缀的文件名的文件来专门定义复杂的内联函数。

函数参数顺序规则

当定义一个函数的时候,其参数的顺序应该为:先是输入,然后是输出。

文件包含的命名和顺序规则

为了易读性和避免隐含依赖,请使用如下标准顺序:c 库,c++ 库,其他库的.h文件,你的工程的.h文件。

#include "base/logging.h"

#include"foo/public/fooserver.h"  //Preferred location.

 

#include <sys/types.h>

#include <unistd.h>

#include <hash_map>

#include <vector>

 

#include "base/basictypes.h"

#include"base/commandlineflags.h"

#include "foo/public/bar.h"

作用域(Scoping

 命名空间

鼓励在.cc文件中不使用命名空间。如果使用命名空间,则其名字最好是基于工程或者路径,而不要使用“using –顶层命名空间”(using-directive)。

Definition:

命名空间将全局域进行了细分,命名了不同的域,以便防止在全局域中发生名称冲突。

Pros:

命名空间提供了一种层次化命名方式,以替代类提供的另一种层次化的命名方式。

例如,如果两个不同的工程在全局域都有一个Foo的类,这就有可能在编译期或者运行期产生标志冲突。当两个工程在同一个命名空间内编码时,project1::Foo 和 project2::Foo就是不同的标志,这样就可以避免标志冲突。

Cons:

Decision:

不使用命名空间:

      允许并鼓励在.cc文件中不使用命名空间,以避免在运行期发生命名冲突。

·        namespace{                           // This is ina .cc file.

·         

·        //The content of a namespace is not indented

·        enum{ kUnused, kEOF, kError };       //Commonly used tokens.

·        boolAtEof() { return pos_ == kEOF; }  // Usesour namespace's EOF.

·         

·        }  // namespace

•    不要在.h等文件中不使用命名空间

使用命名空间

按照如下方式使用命名空间:

•    命名空间覆盖整个源代码文件,位于头文件包含、全局标志声明与定义、其他命名空间类的前向声明之后。

•    // In the .h file

•    namespace mynamespace {

•     

•    // All declarations are within thenamespace scope.

•    // Notice the lack of indentation.

•    class MyClass {

•     public:

•      ...

•      void Foo();

•    };

•     

•    }  //namespace mynamespace

 

•    // In the .cc file

•    namespace mynamespace {

•     

•    // Definition of functions is within scopeof the namespace.

•    void MyClass::Foo() {

•      ...

•    }

•    }  //namespace mynamespace

 

典型的.cc文件一般会有更多的复杂细节和对其他命名空间相关类的需求。

 

•    #include "a.h"

•     

•    DEFINE_bool(someflag, false, "dummyflag");

•     

•    class C; // Forward declaration of class C in the global namespace.

•    namespace a { class A; }  // Forward declaration of a::A.

•     

•    namespace b {

•     

•    ...code for b...         // Code goes against the left margin.

•     

•    }  //namespace b

 

•  不要在std命名空间内声明任何东西,更不要前向声明标准库类。在命名空间std中声明实体是未定义的行为,即这是不适宜的。声明来自标准库的实体,应包含相应的头文件。

•   你应该不使用“using –顶层命名空间”(using-directive),以避免使一个命名空间下的所有命名都有效。

 

•    //禁止 –这将破坏命名空间.

•    using namespace foo;

 

•   .cc文件和头文件的函数、方法及类中,你可以使用using-声明(using-declaration)。

•    // OK in .cc files.

•    // Must be in a function, method or classin .h files.

•    using ::foo::bar;

•   命名空间的别名允许使用在.cc文件的任何地方,和已经命名的覆盖整个.h文件及函数与方法的命名空间之内。

•    //简化在.cc文件中常用的命名

•    namespace fbz = ::foo::bar::baz;

•     

•    //简化在.h文件中常用的命名

•    namespace librarian {

•    // The following alias is available to allfiles including

•    // this header (in namespace librarian):

•    // alias names should therefore be chosenconsistently

•    // within a project.

•    namespace pd_s =::pipeline_diagnostics::sidetable;

•     

•    inline void my_inline_function() {

•      //namespace alias local to a function (or method).

•      namespace fbz = ::foo::bar::baz;

•      ...

•    }

•    }  //namespace librarian

注意头文件中的别名将会对所有包含该头文件的文件有效,所以那些公用头文件(独立在工程之外)和被它们所包含的头文件应该避免定义别名,以便保持API集尽量的小。

嵌套类(Nested Classes

作为接口的一部分,你可以考虑使用公用嵌套类,但应该仔细考虑命名空间,以在全局域中保持清晰的声明。

定义(Definition:)

嵌套类是指在一个类中定义另一个类,亦被称为成员类。

class Foo {

 

 private:

  //Bar is a member class, nested within Foo.

 class Bar {

   ...

  };

 

};

优点(Pros:

 

缺点(Cons):

 

结论(Decision:

除非嵌套类真的是接口的一部分,例如拥有一组方法选项的类,否则不要使它们成为公用的。

非成员函数,静态成员函数和全局函数(Nonmember, Static Member, and Global Functions

相对于全局函数,更应当使用命名空间内的非成员函数或者静态成员函数。纯粹使用全局函数的机会应当很少。

优点(Pros:

 

缺点(Cons:

 

结论(Decision:

定义没有绑定到类实例的函数有时是有用的,甚至是必须的,这样的函数可以是静态成员函数或则非成员函数。非成员函数应该不依赖于外部变量,并且尽量在同一个命名空间下。

如果一个类中只有一组静态成员函数,并且这些函数并不共享静态数据,那么应该使用命名空间来替代这个类。

与生产类定义在同一个编译单元内的函数,当其被其他的编译单元调用时,也许会产生一些不必要的耦合和连接时依赖。静态成员函数则特别能应对这种情况。可以考虑抽象出一个新类,或则在一个单独的库中的一个命名空间内编写函数。

如果你必须定义一个非成员函数,而它只是在它的.cc文件中被用到,可以使用一个没有名字的命名空间,或则静态链接以限制其作用域(例如,static int Foo() {...})。

局部变量(Local Variables

在最小的作用域内编写函数的变量,并且在声明时即初始化。

 

C++允许在函数体内的任何地方声明变量。我们则鼓励尽量在局部域声明它们,并且尽量靠近第一次使用它们的地方。这样就使读者更容易找到声明,知道变量是什么类型,和它是怎样被初始化的。尤其要用直接初始化代替声明和赋值。例如:

int i;

i = f();      // 坏 –-初始化从声明中独立出来了

 

int j = g();  // 好 –-声明与初始化在一起.

 

注意,在GCC编译器里,for (int i = 0; i < 10; ++i)内,i的作用域仅在循环体内,所以你可以在同一个域内的另一个循环体中使用i 。这在 if  和 while 内同样正确。例如:

while (const char* p = strchr(str, '/'))str = p + 1;

警告:如果变量是一个对象,每次它进入域被创建时,其构造函数将被调用,每次它离开域时,其析构函数将被调用。

// 效率低的实现:

for (int i = 0; i < 1000000; ++i) {

  Foof;  // 构造函数和析构函数被调用 1000000次。

 f.DoSomething(i);

}

将这样的变量声明在循环体外更具效率。

Foo f; // 构造函数和析构函数只被调用一次.

for (int i = 0; i < 1000000; ++i) {

 f.DoSomething(i);

}

静态和全局变量(Static and Global Variables

类(class)类型的静态和全局变量原本是被禁止的:由于构造和析构顺序的不确定性,它们将导致出现难寻的BUG。

 

诸如全局变量、静态变量、静态类成员变量和函数体内静态变量等拥有静态持续存储的对象,它们都必须是“普通旧数据”(Plain Old Data,简称POD)。属于POD只有整型、字符型、浮点型、指针型和普通旧数据(POD)的数组或结构体。

 

类的构造和静态变量的初始化的调用顺序在C++ 中只被部分的说明,甚至该顺序还会在每次的构建中发生改变,而这将导致难以发现的BUG。因此除了禁止全局的类类型,我们还不应该允许静态普通旧数据(POD)用函数的返回值来初始化,除非该函数(如getenv(),  getpid())本身并不依赖于任何全局量。

 

类似的,析构的调用顺序与构造的调用顺序刚好相反。既然构造顺序不确定,那么析构顺序也就不确定。例如,当程序结束时,一个静态变量已经被销毁了,但仍运行的代码(也许是另一个线程)试图访问它而失败。再如,静态字符串变量也许会在另一个包含了该字符串引用的变量之前优先销毁。

 

因此,我们仅允许使用包含POD数据的静态变量。这条规则完全不允许使用vector(使用C数组代替),和string(使用constchar []代替)。

 

如果你需要类类型的静态或全局变量,可以考虑在函数main()或者pthread_once()中初始化一个永远不会被释放的指针。注意这个指针必须是个原始指针,而不是智能指针,因为智能指针的析构正是我们所应该避免的析构顺序的问题。

 

类(Classes)

类是C++代码的基本单元,自然它也被广泛的使用着。这章列出主要规则,难道你在编写类时不去遵循它么?

在构造函数中应该做的工作(Doing Work in Constructors)

通常构造函数中应该尽量少的初始化成员变量。任何复杂的初始化都应该使用显式的Init()方法。

 

定义(Definition:

有关在构造体内如何初始化。

优点(Pros:

方便编码,而不用担心类是否被初始化。

缺点(Cons:

这样做在构造函数中可能出现的问题是:

l   

l   

l   

l   

结论(Decision:

如果你的对象需要重要的初始化,那么可以考虑显式的Init()的方法。特别是构造函数不应该调用虚函数、增加错误和访问可能没有初始化的全局变量等等。

默认构造函数(Default Constructors)

如果你的类中定义了成员变量而没有其他的构造函数,那么你就必须定义一个默认构造函数。否则编译器就会替你定义一个,而这并不好。

定义(Definition:

优点(Pros:

缺点(Cons:

结论(Decision:

如果你的类中定义了成员变量而没有其他的构造函数,那么你就必须定义一个默认构造函数(无参数的那个)。你应该使用这种一致而有效的方法初始化你的对象。

 

之所以这样就是因为如果你没有其他的构造函数而又不定义默认构造函数,那么编译器为你构建一个默认构造函数。而编译器构建的默认构造函数并不能智能的初始化你的对象。

 

如果你的类是从一个已经存在的类继承而来,而你并没有添加什么成员变量,那么你就不必定义默认构造函数了。

(ExplicitConstructors)



韩智柜    手机展柜

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值