代码规范
一些个人习惯而罢了。
头文件
分四部分依次排列
cpp 文件对应的头文件
解决方案内源码可见的头文件
第三方库的头文件
标准库头文件
第三方库用 <> 自己的头文件用 “”
按字典序排序
清理不必要的头文件
C++ 标准库头文件不带扩展名,C 头文件带 .h,C++ 程序中尽量用 C++ 头文件
命名
函数大写驼峰
变量小写驼峰
指针和引用右对齐
成员变量 - m_
指针 - p_
local 静态变量 - s_
静态成员变量不加前缀,因为 T::abc 的用法足以说明该变量的类型
编译期常量 - 大写驼峰,或者全大写,下划线连接
缩写 - 全大写
累计值 - count
字节数 - size
字符串长度 - length
注释
单行注释
12// ...int a; // ...
多行注释
12345/* * ... * ... */int a;
// 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
前向声明
如何传参
对于 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
当然也可以拉几把倒全用 std::string_view,扣这么细很麻烦而且不一定有意义
STL 只要提供一堆重载就行,我们调库的要考虑的可就多了(
尽量用 assert 代替 if-log-return,C++ 最好的错误检查就是崩在一个合理的地方
用户错误则用 if-log-return 没有问题
用 std::tuple 和结构化绑定代替一些过分临时的结构体
怎么处理编译期可确定的映射关系
constexpr array(键为或者能够转换为非重复的整数下标)
constexpr / consteval 函数 / Lambda 里的 switch-case
1234auto Lambda = []() constexpr{ //...};