代码规范
一些个人习惯而罢了。
头文件
- 分四部分依次排列
- cpp 文件对应的头文件
- 解决方案内源码可见的头文件
- 第三方库的头文件
- 标准库头文件
- 第三方库用 <> 自己的头文件用 “”
- 按字典序排序
- 清理不必要的头文件
- C++ 标准库头文件不带扩展名,C 头文件带 .h,C++ 程序中尽量用 C++ 头文件
命名
- 函数大写驼峰
- 变量小写驼峰
- 指针和引用右对齐
- 成员变量 -
m_
- 指针 -
p_
- local 静态变量 -
s_
- 静态成员变量不加前缀,因为 T::abc 的用法足以说明该变量的类型
- 编译期常量 - 大写驼峰,或者全大写,下划线连接
- 缩写 - 全大写
- 累计值 -
count
- 字节数 -
size
- 字符串长度 -
length
注释
- 单行注释
1 |
|
- 多行注释
1 |
|
// TODO: Do something
- 首字母大写
- 除非是长篇的描述,否则不加句号
- 使用
does not
而非doesn't
- 公式之类非英文的部分用 `` 括起来
- 对函数、类型的引用以 # 开头
- 命名空间右大括号后跟注释
- 较长的
#else
或#endif
后跟注释 - 单行死代码用
//
,多行用#if 0
- 复数:内容物是否是XXX,并且相互独立
Utils/
,Defines.h
- 单数:内容物是否属于XXX的一部分,并且相互耦合
Renderer/
,Hash.hpp
类型
- 可以使用
int
和char
- 使用
int16_t
而非short
,使用uint64_t
而非unsigned long long
- 使用
true
和false
而非0
和1
来表示 bool - 涉及位运算时使用无符号整数
- 避免对无符号整数进行可能令其下溢的计算
- 与第三方库交互时优先遵循该库的参数 / 返回类型
类
- 考虑到内存对齐,成员变量应按大小降序排序,不需要严格遵守,重点是不要让类的中间出现填充
- 按成员变量的声明顺序排列初始化列表,尽量不要用一个成员变量初始化另一个成员变量
- 成员函数的默认值写在构造函数初始化列表里,没有构造函数则写在声明处
final
- 成员以
static
,public
,protected
,private
的顺序排列 - 三五法则
- 基类析构函数必须为虚函数
- 重写基类虚函数必须加
override
大括号
- 不要省略单行代码的大括号
switch
每个case
都加大括号,大括号内break
/return
其他
-
4 空格缩进
-
命名空间内容不缩进
-
template<typename>
为非template<class>
-
前向声明
-
如何传参
- 对于
sizeof(T) <= 8
的栈类型- 直接 copy
- 对于
sizeof(T) > 8
,的栈类型const &T
,禁止对其移动
- 对于会产生动态内存分配的类型
- 提供普通参数,内部必须 move,由函数调用者决定 copy 进函数还是 move 进函数
- 禁止用
std::string_view
参数构造std::string
- 禁止用
std::span
/std::initializer_list
参数构造std::vector
- 如果参数在函数内不存在直接的构造 / 赋值行为
const &T
- 很多时候我们只需要
void *
和 size,优先用std::span
- 如果只需要一个
char *
,优先用const char *
- 如果同时需要
char *
和 size,优先用std::string_view
- C-style string,
std::string
和std::string_view
会让情况变得复杂- copy 一个
char *
比 copy 一个std::string_view
快一丢丢,但是缺失了长度信息 - 比较糟糕的情况就是
std::string.data()
传给char *
参数,然后在内部以某种方式重新计算长度,产生的开销可能得不偿失- 更糟糕的是我们不一定能简单地判断这件事,举个例子,C++20 MSVC STL:
std::fstream
就算用std::string
构造也只会将参数的.data()
转发给接受char *
的重载std::filesystem::path
用char *
构造则会在内部用 for 循环计算长度并构造std::string_view
- 换个编译器实现说不定就不一样了,换个 C++ 版本说不定也不一样了
- Anyway:
- if 调用处一定只存在
char *
- 用
const char *
,真要计算长度早晚逃不掉,不如少 copy 点东西
- 用
- else
- if 函数内不需要长度
- 用
const char *
- 用
- else
- 用
std::string_view
- 用
- if 函数内不需要长度
- 当然也可以拉几把倒全用
std::string_view
,扣这么细很麻烦而且不一定有意义 - STL 只要提供一堆重载就行,我们调库的要考虑的可就多了(
- if 调用处一定只存在
- copy 一个
- 对于
-
尽量用 assert 代替 if-log-return,C++ 最好的错误检查就是崩在一个合理的地方
- 用户错误则用 if-log-return 没有问题
-
用
std::tuple
和结构化绑定代替一些过分临时的结构体 -
怎么处理编译期可确定的映射关系
- constexpr array(键为或者能够转换为非重复的整数下标)
- constexpr / consteval 函数 / Lambda 里的 switch-case
1
2
3
4auto Lambda = []() constexpr
{
//...
};