google开源项目风格指南-C++
引言
自从大学开始接触c++语言(平时写的都是类C代码),断断续续的已经有7、8年过去了,但是,始终没有规范的在工程上使用C++。现在的工作主要使用python语言,更多的是关注算法,而没有关注语言本身。但是python项目也给我带来了工程经验,经验之一就是,代码是写给别人看的,大家遵守共同的编程规范很重要。所谓的编程规范,也可以称为编程风格,一开始会让新手感觉是桎梏,但随着解了越来越多的bug,写了越来越多的代码,也体会到了,编程规范的而重要性,编程规范不但是一种风格,而且是避免埋坑、减少bug的重要措施。如果大家写的代码一致性高,那阅读起来也就方便多了。
在网上搜索了google的编程规范,应该是值得一读的,因为这些规范都是大厂内的有丰富工程经验的人总结出来的精华。因此,准备认真研读,并且,测试为什么这样规定。
头文件
一般来讲,每个cpp文件都应该对应一个.h文件(除了只包含main函数的文件),这是为了提升可读性与可维护性。一般头文件要遵守以下规范:
- 头文件要加入#define保护,避免重复编译时导入;
- 头文件只能声明变量,不能定义变量,全局的变量不可以声明,因为全局变量在声明时会初始化一个默认值。两次包含给头文件,编译时,会报重复定义的错误;
- 头文件的包含顺序为自身头文件、c标准库、c++标准库、其他项目头文件、自身项目其他头文件;
1 | /* 为了防止被多重导入,header文件要有#define保护 */ |
作用域
命名空间
命名空间(namespace)可以把全局作用域细分为具名独立的作用域,使用命名空间,可以避免一些通用命名的冲突。但两个不同命名空间的相同变量名称放在一起会有迷惑性,而且不得不引用多层嵌套命名空间内的定义会使代码变得冗长。
命名空间的两个特殊特性:
内联命名空间:内敛命名空间会自动把标识符放在外部命名空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15namespace foo1{
inline namespace foo2 {
int c = 3;
} //foo2
int a = 1, b = 2;
void print() {
std::cout << a * b * c << std::endl;
}
} // foo1
foo1::c 与 foo1::foo2::c一致匿名命名空间:编译器会给匿名命名空间生成一个独立不可见标识,导致,其他文件不能引用匿名命名空间内的变量,两个不同匿名命名空间的相同定义不会冲突(两个不同cpp文件)。
1
2
3namespace {
...
} //namespace
规范:
- 禁止使用内联命名空间,在大型版本控制里面使用
- 鼓励在
.cc文件中使用匿名命名空间或static声明 - 不能在header里面定义匿名命名空间,会违背唯一定义原则
- 不能使用using指示命名空间
- 不要在std里面声明任何任何东西,否则会导致不可移植
- 不要在头文件中使用命名空间别名
非成员函数、静态成员函数、全局函数
使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
局部变量
将函数的变量尽可能置于最小的作用域内,并在变量声明时进行初始化。
不要在循环内部初始化对象,否则每次调用构造函数与析构函数导致执行效率很低
静态与全局变量
禁止定义静态存储周期的非POD(Plain Old Data),POD数据如int、char、float,多编译单元中的静态变量执行的构造与析构顺序时未明确的。也不使用vector和string类型的静态变量。
类
构造函数
不要再构造函数内调用虚函数,也不要进行可能会失败的初始化。因为这样不用考虑类是否被正确的初始化,经过构造函数初始化的类型可以是const类型,也可以方便的被其他容器调用。
如果调用虚函数,这类调用不会重定向到子类的虚函数实现;如果初始化可能会报错,则初始化的对象可能进入不正常的状态,必须调用bool isValid()判断,而这个方法容易被忽视;构造函数的地址是无法被获得的,有构造函数完成的工作无法以简单的方式交给其他线程。
运算符重载
除少数特定环境下,不要重载运算符,这与《More Effective C++》里面第5条款的要求是类似的。
运算符重载是指,c++允许用户使用operator关键字对内建运算符进行重载定义,operator允许用户使用operator""定义新的字面量,并且定义类型转换函数如:
1 | operator bool(); // 重载bool类型转换 |
重载运算符可以使代码更简洁易懂,使用户自定义的类型与内建类型拥有相似的行为。
但是过度使用操作运算法重载,会导致令人迷惑的bug。
不要为了避免重载操作符而走极端. 比如说, 应当定义 ==, =, 和 << 而不是 Equals(), CopyFrom() 和 PrintTo(). 反过来说, 不要只是为了满足函数库需要而去定义运算符重载. 比如说, 如果你的类型没有自然顺序, 而你要将它们存入 std::set 中, 最好还是定义一个自定义的比较运算符而不是重载 <.
不要重载 &&, ||, , 或一元运算符 &. 不要重载 operator"", 也就是说, 不要引入用户定义字面量。
其他
- 如果不需要拷贝,则把默认拷贝构造函数禁掉,使用
= delete; - 不要使用多继承,少使用继承。