Contents

阅读谷歌编程规范

事情的起因

好的东西就是这样,你每次看都能够有新的收获。 这次也一样,读了一遍谷歌编程规范,又有些新的理解,也有一些需要重新让自己记住的点。 遂记录下来,成此博文。

头文件

#define保护

谷歌的格式是 当是: _ _ H 为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目 foo 中的头 文件 foo/src/bar/baz.h 按如下方式保护:

1
2
3
4
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

比较起来我们的格式是使用项目前缀来防止重复,考虑到我们项目的规模一般是不会重复的,因为文件命名上就不一致。

头文件依赖

尽量使用前置声明减少头文件的依赖。 因为引入一个新的头文件,头文件改变时,包含这个头文件的其他头文件也会被重新编译。 1.强数据成员声明为Foo* Foo& 2.参数、返回值类型为Foo的函数,只是声明 3.今天数据成员的类型可以被声明为Foo,因为静态数据成员定义在类定义之外 至于使用指针成员替代成员对象,则会降低可读性,执行效率,不要这样做。

内联函数

少于十行的函数定义为内联函数。 小巧的代码更好的利用指令缓存。 短小的内联函数直接放在.h文件中,对于比较复杂的内联应该放在-inl.h文件中

函数参数的顺序

输入参数在前,输出在后。

包含文件顺序

C库 C++库 其他库 项目内的 头文件应该有带有目录信息,不要使用当前目录和父目录

1
2
3
4
5
6
7
8
9
举例来说, google-awesome-project/src/foo/internal/fooserver.cc 的包含次序如下 :
#include "foo/public/fooserver.h" // 优先位置
#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"

作用域

命名空间

在.CC文件中,允许甚至提倡使用不具名命名空间,以避免运行时候的命名冲突。 最好不要使用using,不要声明命名空间std下的恩和内容。

嵌套类

公开嵌套类作为接口的一部分时候,虽然可以直接放在全局作用域中,但是最好还是放在命名空间里。 使用起来的话,

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

尽量放在命名空间里。

局部变量

将函数变量尽可能置于最小作用域内,在声明变量时候将其初始化。靠近第一次使用,利于阅读。

全局变量

全局变量的构造函数、西沟函数以及初始化操作的调用顺序只是被部分规定,每次生产可能会有变化。 很多可以用单例模式替代。

构造函数中只进行哪些没有实际意义的初始化。在Init中集中初始化有意义的数据。

构造函数的问题是,没有异常处理。

明确的构造函数

使用explicit,防止自动转换。

拷贝构造函数

大量类并不要拷贝构造函数,所以应该使用DISALLOW_COPY_AND_ASSIGN来防止拷贝构造函数的自动生成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 禁止使用拷贝构造函数和赋值操作的宏
// 应在类的 private:中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};

继承

使用组合一般都比使用继承合适,继承只使用public继承 虚析构函数只在有继承同时有虚函数的时候使用。

接口

接口是指满足特定条件的类,这些类以Interface为后缀 定义:纯接口

只有纯虚函数和静态函数 没有非静态数据成员 没有定义任何构造函数,如果有,也不含参数,并且为protected 如果是子类,也只能继承满足上述条件并以Interface为后缀的类

操作符重载

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

混淆你的直觉,让你误以为费时的操作和内建操作一样轻巧 查找重载操作符的调用处困难 有的操作符可以对指针进行操作 重载的副作用,重载操作符&的类不能被前置声明

声明次序

在类中使用特定的声明次序:public:在 private:之前,成员函数在数据成员(变量)前。 定义次序如下:public:、protected:、private:,如果那一块没有,直接忽略即可。 每一块中,声明次序一般如下:

  1. typedefs 和 enums;
  2. 常量;
  3. 构造函数;
  4. 析构函数;
  5. 成员函数,含静态成员函数;
  6. 数据成员,含静态数据成员。 宏 DISALLOW_COPY_AND_ASSIGN 置于 private:块之后,作为类的最后部分。参考 拷 贝构造函数。 .cc 文件中函数的定义应尽可能和声明次序一致。 不要将大型函数内联到类的定义中,通常,只有那些没有特别意义的或者性能要求高的,并 且是比较短小的函数才被定义为内联函数。更多细节参考译文第一篇的 内联函数。

编写短小函数

超过40行,考虑分割。

Google特有的风情

智能指针

需要使用智能指针的话scoped_ptr完全可以胜任。特殊情况下使用share_ptr。不要使用auto_ptr。 倾向于设计对象隶属明确的代码。最明确的对象隶属是根本不使用指针,直接将对象作为一个域或者局部变量使用。

其他C++特征

引用参数

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

缺省参数

禁止使用缺省函数参数,所有参数必须明确指定的话,避免程序员不知道存在的缺省参数。

编程数组和alloca

禁止使用变长数组。使用安全的分配器。

友元

将一个单元测试用类声明为待测类的友元,很方便。

不使用C++异常

不使用RTTI

直接利用虚函数处理不同类型就好了。

类型转换

使用 static_cast 比较好处理,直接查找static_cast,就能找到哪里用了转换。C语言转换语义模糊。

  1. static_cast:和 C 风格转换相似可做值的强制转换,或指针的父类到子类的明确的向上 转换;
  2. const_cast:移除 const 属性;
  3. reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做一 切了然于心时使用;
  4. dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信 息,说明设计有缺陷(参考 RTTI)。

流 streams

只在记录日志的时候使用。 其他时候使用printf替代。 估计是stream的构造使用成本高,printf简单直接。 然后steam重载«会产生很多意想不到的错误,在深入理解C++面向对象的书里面也有提到。

前置自增和自减

效率更高。 对于简单数值来说无所谓,但是对于迭代器这种,前置更好。

const的使用

在能够使用const的时候使用const.

整型

C++中使用 stdint.h中的确定大小整型 不要使用无符号,使用断言声明变量非负数。也就是在传参的时候,在执行前断言判断。

预处理宏

宏尽量被内联函数、枚举和常量替代 下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考:

  1. 不要在.h 文件中定义宏;
  2. 使用前正确#define,使用后正确#undef;
  3. 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
  4. 不使用会导致不稳定的 C++构造(unbalanced C++ constructs,译者注) 的宏, 至少文档说明其行为。 像我们项目里的宏,完全应该被替代,起码使用一个命名空间,防止污染全局空间事情的发生。

命名约定

通用命名规则

不缩写 易于理解第一

注释

TODO注释

1
2
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

格式

swich

如果 default 永不会执行,可以简单的使用 assert:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
switch (var) {
case 0: { // 2 space indent
... // 4 space indent
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}