[CPP] Coding Style

时间:2022-11-24 10:38:30

C++ Coding Style

C++很多强大的语言特性导致它的复杂,其复杂性会使得代码更容易出现bug、难于阅读和维护。

由于,本人有一点点代码洁癖,所以依照Google的C++编程规范《Google C++ Style Guide》,用来约束自己平时编程,使得代码在有效使用C++语言特性的同时易于管理。

分类

标题

规则

备注(示例)

头文件

每.cpp文件都应对应一个.h(.hpp)文件

#define保护

1.  #define PROJECT_PATH_FILE_H_ 防止.h文件被多重包含;

Project sdk中.h文件sdk/src/abc.h,#define如下:

 #ifndef SDK_SRC_ABC_H_
#define SDK_SRC_ABC_H_
...
#endif // SDK_SRC_ABC_H_

前置声明

1. 使用前置声明尽量减少文件中#include的数量;

2. 使用函数时,采用#include方式;

3. 使用类模版时,采用#include方式;

4. 使用普通类时,采用前置声明;

5. 数据成员为类自身的指针或引用时,采用前置声明;

可以依赖声明,就不要依赖定义;

内联函数

1. 不要内联超过10行的函数;

2. 析构函数应慎重对待;

3. 内联包含循环或switch语言的函数将得不偿失;

4. 虚函数和递归函数即使被声明为内联也不一定是内联函数;

-inl.h函数

1. 复杂的内联函数定义,放在后缀名为-inl.h的头文件中;

函数参数顺序

1. 函数参数顺序:输入参数在前,输出参数在后;

1. 输入参数为传值或常数引用、常指针;

2. 输出参数为非常数指针、非常数引用;

包含文件的名字和顺序

1. 包含.h文件次序:优先的.h文件、C库、C++库、其他库的.h、项目内的.h;

2. 项目内.h文件应按照项目源代码目录树结构排列,并且避免使用UNIX目录.(当前目录)和..(父目录);

3. 相同目录下.h文件按字母序排列;

1. google-awe-project/src/base/logging.h应这样被包含:

#include “base/logging.h”;

2. 某foo.cc(或foo.cpp)文件中包含.h文件次序:

 #include “foo/public/foo.h” // 优先.cc或.cpp对应的头文件
#include <sys/types.h> // C库
#include <hash_map> // C++库
#include <foo/public/bar.h> // 项目内头文件

作用域

作用域

1. 在.cpp文件(不能在.h文件)中,使用不具名的命名空间;

2. 具名命名空间的名称基于项目或路径名称;

3. 不要使用using指示符,避免污染命名空间,可以使用using;

4. 在.cpp文件、.h文件中的函数和类中,可以使用using;

5. 在.cpp文件、.h文件中的函数和类中,可以使用命名空间别名;

6. 不要使用inline namespace;

1. 在.cpp文件中的不具名命名空间:

 namespace
{
enum {UNUSED, EOF, ERROR}; // 无缩进
bool foo() {return EOF;}
} // namespace

2. 具名命名空间:

 namespace mynamespace
{
class MyClass
{
public:
void foo();
};
} // namespace mynamespace

嵌套类

1. 嵌套类不作为接口使用时,不要定义为public;

2. 嵌套类作为接口的一部分时,置于命名空间中;

非接口嵌套类:

 class Foo
{
private:
class Bar
{
};
};

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

1. 使用命名空间中的非成员函数或静态成员函数,尽量不使用全局函数;

2. 若确定需要定义非成员函数,并且只在.cpp文件中使用它,可使用不具名命名空间或static关联限定其作用域;

局部变量

1. 将函数变量尽可能置于最小作用域内,在声明变量时将其初始化;

静态和全局变量

1. 禁止class类型的全局(静态)变量,若一定要使用,可单例模式;

2. 多线程代码中禁止非常数全局变量,不可使用函数返回值初始化全局变量;

3. 全局字符串常量,使用C风格字符串,而不要使用STL字符串;

4. 大多数全局变量应该是类的静态数据成员,或当其只在.cpp文件中使用时,将其定义到不具名命名空间中,或者使用静态关联以限制变量的作用域;

5. 静态成员变量视作全局变量,不能是class类型;

C风格字符串常量:

 const char kFrogSays[] = “ribbet”;

构造函数职责

1. 构造函数只进行那些没有实际意义的初始化;

2. 如果对象需要有意义的初始化,可以采用工厂函数或者Init()方法集中初始化;

3. 构造函数禁止调用虚函数;

初始化

1. 若类中定义了成员变量,没有提供其他构造函数,需要定义一个默认构造函数;

显式构造函数

1. 除非必要,单参数构造函数使用C++关键字explicit;

 explicit Foo(string name);

拷贝构造函数

1. 仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数,不需要拷贝时应使用DISALLOW_COPY_AND_ASSIGN;

可以考虑在类的private中添加空的拷贝构造函数和赋值操作,并且只有声明,不进行定义;

 #define DISALLOW_COPY_AND_ASSIGN(Type) \
Type(const Type&); \
void operator = (const Type&) class Foo
{
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};

结构体和类

1. 仅当只有数据时使用struct,其它情况使用class;

2. 对于functor和trait,可以使用struct;

3. 类和结构体的成员变量使用不同的命名规则(见下面的命名约定);

继承

1. 所有继承必须为public继承,采用基类对象作为成员的方式替代私有继承;

2. 不要过多使用实现继承,更多使用组合;

3. 使析构函数为virtual,如果该类具有虚函数,其析构函数一定为虚函数;

4. 限定仅在子类访问的成员函数为protected,数据成员应始终为私有;

5. 重定义派生的虚函数时,在派生类中明确声明其为virtual;

多继承

1. 只有当最多一个基类中含有实现,其他基类都是以Interface为后缀的纯接口类时才使用多继承;

2. 纯接口必须以Interface为后缀;

接口

1. 满足纯接口要求时,类以Interface结尾;

2. 接口类必须声明虚析构函数,析构函数不能纯虚函数;

满足纯接口类的要求:

* 只有纯虚函数和静态函数(析构函数除外);

* 没有非静态数据成员;

* 没有定义任何构造函数,若有,须不含参数,且为protected;

* 如果是子类,只能继承满足以上条件并以Interface为后缀的类;

操作符重载

1. 一般不要重载操作符,如果需要的话,可以定义类似Equal()、CopyFrom()等函数;

2. STL容器中作为key要重载operator==或operator<,可以在声明容器的时候,创建相等判断和大小比较的仿函数类型;

访问控制

1. 将类数据成员设为private,并提供相关存取函数;

2. 存取函数的定义一般内联在头文件中;

定义变量m_foo及其取值函数foo()、赋值函数setFoo();

声明顺序

1. 类中定义次序:public:、protected:、private:;

2. 每一块中,声明次序为:typedef和enum、常量(static const数据成员)、构造函数、析构函数、成员函数(含静态成员函数)、数据成员(除static const数据成员);

3. 宏DISALLOW_COPY_AND_ASSIGN置于private:块之后,作为类的最后部分;

4. .cc文件中函数的定义顺序和声明次序一致;

编写短小函数

1. 如果函数超过40行,可以考虑在不影响程序结构的情况下将其分割;

其它C++特性

智能指针

1. 任何情况下禁止使用auto_ptr;

引用参数

1. 所有按引用传递的参数必须为const引用;

2. 输入参数采用const引用,输出参数采用指针;

3. 输入参数可以是const指针,但不可以是non-const引用;

函数重载

1. 仅在输入参数类型不同、功能相同时使用重载函数;

缺省参数

1. 禁止使用缺省参数;

变长数组和alloca

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

友元

1. 可以合理使用友元类及友元函数;

异常

1. 禁止使用异常;

运行时类型识别

1. 除单元测试外,禁止使用RTTI;

类型转换

1. 使用C++风格而不要使用C风格类型转换;

2. 除单元测试外不要使用dynamic_cast;

使用static_case <> ()等C++的类型转换;

1. 除日志接口外,使用printf之类的代替流;

最好选择printf + read/write;

前置自增和自减

1. 对于迭代器和其他模板对象使用前缀形式自增和自减运算符;

const使用

1. 在任何可以使用的情况下使用const;

整型

1. 使用断言声明变量为非负数,不要使用无符号型;

64位下的可移植性

1. printf指定的一些类型在32位和64位系统上可移植性不是很好;

2. sizeof(void *) != sizeof(int),可以用intptr_t定义指针大小的整数;

3. 结构体字节对齐;

4. 创建64位常量时使用LL或ULL作为后缀;

int64_t my_value = 0x123456LL;

预处理宏

1. 慎用宏,尽可能以内联函数、枚举和常量代替;

2. 在.h文件中,除了#define防止头文件重包含外,不要定义宏;

0NULL

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

sizeof

1. 尽可能用sizeof(varname)代替sizeof(type);

Boost

1. 只使用Boost中被认可的库:

Compressed Pair:boost/compressed_pair.hpp;

Pointer Container:boost/ptr_container,其中不包括ptr_array.hpp和serialization;

命名约定

最重要是一致性

通用命名规则

1. 函数、变量、文件命名应具有描述性,不要过度缩写,类型和变量应是名词,函数名可以用“命令性”动词;

2. 除非放到项目外也非常明了,否则不要使用缩写;

1.  int num_completed_connections;

2.  int num_dns_connections;

3.  int error_count; // Good.

int error_cnt;   // Bad.

文件命名

1. 文件名要全部小写,可以包含’_’或’-’,按项目约定来;

2. 源文件以.cc结尾,头文件以.h结尾;

可接受的文件命名:

my_useful_class.cc

my-useful-class.cc

myusefulclass.cc

类型命名

1. 类型命名每个单词以大写字母开头,不包含下划线;

1. 类型:类、结构体、类型定义(typedef)、枚举;

2. 如:MyExcitingClass、UrlTable;

变量命名

1. 变量名一律小写,单词间以下划线相连,类的成员变量以下划线结尾;

2. 结构体数据成员可以和普通变量一样,不用像类的成员数据那样以下划线结尾;

3. 全局变量以g_作为前缀;

1.

 my_exciting_local_variable
my_exciting_member_variable_

2.

 struct UrlTablePoperties
{
sting name;
int num_entries;
};

常量命名

1. 在名称前加k;

const int kDayInAWeek = ;

函数命名

1. 普通函数为大小写混合,存取函数则需要与变量名匹配;

2. 其它短小的内联函数可以使用小写字母;

 MyExcitingMethod()
set_my_exciting_member_variable()

命名空间

1. 命名空间全部为小写;

枚举命名

1. 枚举值全部大写,单词间以下划线相连;

宏命名

1. 如果要使用,其命名方式与枚举命名一致;

代码注释

注释风格

1. 使用///* */,统一就好;

文件注释

1. 在每一个文件开头加入版权公告,然后是文件内容描述;

类注释

1. 类的定义要附着描述类的功能和用法的注释;

2. 如果类的实例可被多线程访问,使用时务必文档说明;

函数注释

1. 函数声明处注释描述函数功能,定义处描述函数实现;

2. 构造/析构函数前无需注释;

1. 函数声明处注释的内容:

* inputs及outputs;

* 类成员函数:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;

* 如果函数分配了空间,需要由调用者释放;

* 参数是否可以为NULL;

* 是否存在函数使用的性能隐忧;

* 如果函数是可重入的,其同步前提是什么;

2. 函数定义处定义的内容:

注释说明函数功能和实现要点;

变量注释

1. 通常变量名本身足以很好说明变量用途,特定情况下,需要额外注释说明;

实现注释

1. 对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释;

2. 相邻几行都有注释的,可以调整使//纵向对齐;

TODO注释

1. 对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用TODO注释;

// TODO(hanyp@126.com): change this.

格式

行长度

1. 每一行代码字符数不超过80;

空格

1. 只使用空格,每次缩进2个字符。设定编译器将Tab转为空格;

函数声明和定义

1. 函数名、返回类型、参数尽可能在同一行;

2. 如果函数为const的,const应与最后一个参数位于同一行;

3. 独立行的参数保持4个空格的缩进;

 ReturnType ClassName::FunctionName(Type ar_name1, Type par_name2)
{
DoSomething();
}

函数调用

1. 同函数声明和定义格式;

条件语句

1. 不要再圆括号内加空格;

 if (condition)
{
...
}
else
{
...
}

循环和选择语句

1. 类比条件语句;

指针和引用表达式

1. 句点(.)或箭头(->)前后不要有空格,指针/地址操作符(*、&)后不要空格;

1. 在声明指针、引用变量或参数时,(*、&)与类型名紧挨;

布尔表达式

1. 如果一个布尔表达式超过标准行宽,断行要统一;

函数返回值

1. return表达式中不要使用圆括号;

变量及数组初始化

1. 使用()格式;

int x();

预处理指令

1. 预处理指令不需要缩进;

类格式

1. public、protected、private不需要缩进,函数及变量定义缩进2空格;

初始化列表

1. 构造函数初始化列表放在同一行或按四格缩进并排几行;

2. ‘:’前后各空一格;

命名空间格式

1. 命名空间内容不需要缩进,命名空间不添加额外缩进层次;