实习——C++ coding style
问题定义
如何在 ficus 中写出高质量的 C++ 代码?
高质量:一致性、可读性、可维护性、bug-free
问题分类
- 符合 ficus 大部分代码,补充用于强制的规范
- 缺少现有规范,代码质量参差不齐,为解决现有问题需要补充的规范
目录
- 代码组织
- 头文件
- 作用域
- C++ 特性
- 类
- 函数
- 错误处理/异常
- 其他
- 命名规范
- 注释规范
- 格式规范
头文件
- 头文件应该以 .h 作为扩展名
- 头文件本身应该可以被编译
- 头文件需要通过宏来保证不被重复包含
#include
guard
所有的头文件都需要使用 #define (而不是 #pragma once)来防止被多次包含。使用的宏的名字需要符合 <PROJECT>_<PATH>_<FILE>_H_
的格式。
例如,ficus/common/utility/time_utility.h 应该由以下方式来防止被多次包含:
#ifndef FICUS_COMMON_UTILITY_TIME_UTILITY_H_
#define FICUS_COMMON_UTILITY_TIME_UTILITY_H_
...
#endif // FICUS_COMMON_UTILITY_TIME_UTILITY_H_
前向声明
避免使用前向声明,使用 #include
内联函数
只应该内联足够小的函数,比方说 10 行以下
作用域
命名空间
- 避免使用
using namespace
- 命名空间结束需要有注释
namespace foo
{
namespace bar
{
...
} // namespace bar
} // namespace foo
匿名命名空间
在 .cpp 文件中使用匿名命名空间来定义只在这个文件中使用的函数或者类,不要在 .h 中使用匿名命名空间。
namespace
{
...
} // namespace
类
构造函数
- 避免在构造函数中调用虚函数
- 如果你无法报告错误,避免在构造函数中执行会出错的操作
隐式类型转换
- 在不需要隐式类型转换的构造函数上加上
explicit
拷贝和移动
如果类不应该被拷贝或者移动,需要显式防止其被拷贝或者移动。否则,如果类有自定义的析构函数,需要显式的定义拷贝和移动函数。
class X
{
X(const X&) = delete;
X& operator=(const X&) = delete;
};
struct
vs class
struct
用来保存数据,只包含 public
的数据成员和可选的用于简化数据操作的方法(例如 Reset
),其他所有的类型都应该用 class
来定义。作为例外,仿函数(functor)和 traits 可以使用 struct
来定义。
运算符重载
原则上不允许重载的运算符不符合其常规语义。
访问控制
类的数据成员应该是 private
的
错误处理/异常
- 需要检查所有的错误返回,不要隐式的忽略错误
- 异常的发生对应于 FATAL 错误,除了调用外部库函数产生的异常之外,不要
catch
异常 - 程序代码中不允许抛出异常,在有必要的情况下,使用
LOG(FATAL)
而不是异常。 - 代码应该是异常安全的,也就是说如果调用的函数抛出的异常在外部被捕获不会导致副作用出现
- 使用 RAII 来管理资源(
shared_ptr
和unique_ptr
)
- 使用 RAII 来管理资源(
其他 C++ 特性
- 避免使用 RTTI
- 总是在输入参数上加上 const
命名规范
- 文件:全小写,以下划线分割;可执行文件应该以 _unittest, _functest, _preftest, _systest 或者 _ficus 结尾
- 类型:每个单词的首字母都大写
- 变量:除了第一单词以外首字母大写
- 类私有成员变量:所有单词首字母大写,添加前缀 m
- 常量:所有单词首字母大写,添加前缀 k(和现有做法不同,可以避免和宏重名)
- 命名空间:全小写,以下划线分割
- 枚举项:所有单词首字母大写,添加前缀 k(和现有做法不同,可以避免和宏重名)
- 宏:全大写,以下划线分割
- 例外:如果和 C/C++ 中已有的东西类似,可以使用那些东西的命名规范,例如:
-
sparse_hash_map
类似与 STL 容器。对应于 STL 命名规范
-
注释规范
采用 /** */
的方式写大段注释。参见Confluence 页面
大段文本的注释
大段文本的注释,如果文本有多行,中间可以使用 <br>
来分割行
层级标题
使用 #
表示一级标题##
表示二级标题###
表示三级标题, 依次类推,支持6级标题
项目列表
使用 *
表示一组项目列表,也可以使用数字
特殊标记语法
@note @warning @deprecated @param 都是可以直接使用的标记。
代码段落
class WahahaService : public WahahaServiceIf
{
...
}
参考
https://www.stack.nl/~dimitri/doxygen/manual/markdown.html
格式规范
行长度
- 行长度控制在 120 字符以内
-
#include
、#include
guard 可以超过限制 - 如果将注释换行会影响可读性,可以不换行(例如注释中包含长度超过 80 的 url)
非 ASCII 字符(中文)
- 避免在文件中使用非 ASCII 字符,如果需要,请保证其编码为 UTF-8
缩进
使用 4 个空格(而不是制表符)来缩进。断行用 8 个空格。
函数的声明以及定义
如果一行可以放下,返回类型,函数名和参数应该在同一行
语句格式
if (condition) // 括号内没有空格
{
... // 4 格缩进
}
else if (...) // else 在新行上,和之后的 if 在同一行上
{
...
}
else
{
...
}
指针和引用
点和箭头运算符周围不要加空格
x = *p;
p = &x;
x = r.y;
x = r->y;
类格式
- 依照 public, protected, private 的顺序排列成员。
- 访问控制符不需要缩进。除了第一个访问控制符以外需要在前面空一行。
命名空间格式
命名空间内部不缩进
空格
- 大括号之前应该有空格(不包含初始化器)
- 如果大括号之间没有代码,其中不要加空格
- 如果大括号之间有代码,那么代码和大括号之间应该有空格
循环和条件
if (b)
{
}
else
{
}
while (test) {}
switch (i)
{
}
for (int i = 0; i < 5; ++i)
{
}
运算符
// 赋值运算符的两侧需要有空格
x = 0;
// 二元运算符周围通常需要加上空格
// 利用去除空格表达更高优先级的方法是可行的
// 括号内侧不应该有空格
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 一元运算符和操作数之间不要有空格
x = -5;
++x;
if (x && !y)