1.1 头文件均使用 #define guards
#ifndef <PROJECT>_<PATH>_<FILE>_H_
#define <PROJECT>_<PATH>_<FILE>_H_...#endif // <PROJECT>_<PATH>_<FILE>_H_
1.2 包含所有使用到的头文件
只要头文件中引用了其他文件中的符号,就需要包含该文件;(即使通过传递包含已经包含了该文件)
例如:foo.cc 应该包含 bar.h (即使 foo.h 中包含了 bar.h)
1.3 前向声明
一般使用前向声明的地方,都是使用指针(指针占用空间固定的,不用包含头文件);
但前向声明会隐藏类之间的关系(例如继承关系),这在匹配函数时就可能引发不同的行为;
1.4 内联函数不要超过10行
注意析构函数,因为隐式的成员变量和基类析构函数调用,析构函数往往比它们看起来的要长!
不要包括循环 或 switch
1.5 头文件顺序
(1)项目中的头文件应该带路径(src 目录下直到该文件的路径,不要使用 . 或 . . )
最直接关联头文件(例如在 foo.cc 文件中,此处为 foo.h,以下每类均用空行分隔)
C库
C++库
其他库
该项目其他头文件
(2)在每个模块,应该按字母顺序排序
2. 作用域
2.1 命名空间
(1)不要使用 using 语句;
不要使用 inline namespaces;(会自动把内部的标识符放到外层作用域)
在头文件之后,包含整个源文件;
(2)其名字中,字母全部小写,单词使用下划线分隔;避免缩写
Top-level namespace 应该基于项目名字;
(3)不要在头文件中使用命名空间别名,除非其作用域是局部的(例如在函数内部)
命名空间中内容不需要缩进;
2.2 匿名命名空间 (Internal Linkage)
不会被外部文件使用的变量,可以将它们放入匿名命名空间中,或声明为 static ,不要在 .h 文件中使用这两种方法;
(1)2.3 ~ 2.5 还需要进一步学习
2.3 函数和变量
(1)非成员函数尽量放到某个命名空间中;
(2)声明时就初始化变量
2.4 静态变量和全局变量
const 代表 read-only , 但这并不意味着它是不可变的,也不意味着值总是相同的。
常量随着程序的运行其值不会改变;
C++17 变量可以被声明为 inline,来保证只有一份;
2.5 线程变量
thread_local 变量实际是一些对象的集合,不同的线程实际上访问不同的对象。
在类或命名空间中必须使用编译时常量初始化;
3. 类
3.1 构造函数
构造函数中不要调用虚函数,不要抛出异常(可能失败的初始化操作);
3.2 避免隐式转换
不要定义隐式转换,必须加上 explicit 修饰单参数构造函数和转换操作符;(这将阻止 隐式转换和列表初始化语法)
3.3 拷贝/移动
一个可复制的类应该显式声明复制操作(即使使用 = default
语法),一个仅移动的类应该明确声明移动操作,而一个不可复制/可移动的类则应该明确删除复制操作。
可复制的类也应该声明移动操作;
基类没有 private 部分,其可复制性/移动性由成员变量决定;
如果基类是不可复制/移动的,子类也是;
3.4 继承
尽可能使用组合关系,不得不使用继承的话,使用 public 继承;
虚函数最好都标注上 override 或 (less frequently) final
3.5 类成员变量都应该为 private,除非为常量;
3.6 命名规则
**全部小写,下划线区分单词; **
(1)类中成员变量最后有一个额外下划线;
(2)常量命名
const 或 constexpr 如果整个程序运行期间其值不变;以 k 开头;
(3)函数命名(类型名均采用这种方式 classes, structs, type aliases, enums)
每个单词首字母大写,不使用下划线
get 和 set 函数可以像变量一样命名
(4)枚举类型
enum class UrlTableError { kOk = 0, // 类似常量以 k 开头,每个首字母大写kOutOfMemory,kMalformedInput,
};
3.7 注释
使用 空格 而非 Tab,可以设置 IDE 一个 Tab 改为2个空格
4. 函数
4.1 参数
仅输入作用的参数放在最前面;
(1)non-optional (必须传入的参数)
input parameters 使用 值传递 或 const 引用传递;
output and input/output parameters 使用引用传递;
(2)optional (可传可不传)
input parameters : 值传递时使用 std::optional
output and input/output parameters :使用 non-const 指针;
4.2 尽量小的函数
最好不要超过 40 行,如果超过拆分成小函数;
4.3 类型转换
除非转换为 void,否则都使用 cast ;
数值类型,使用int64_t{x}
intptr_t
指针大小的整数
char 类型的空使用 ‘\0’ ;
4.4 类型推导
如果使得代码更清晰或安全,才会使用。
例如在 auto ptr = make_shared<xxx>
中
4.5 switch
应该总是设置 default
凡是需要顺序往下执行的(不是执行完 case 后就 break 的情况),需要添加 [[fallthrough]];
二、现代C++
1. 类型推导
1.1 模板类型推导
template<typename T>
void f(ParamType param);...f(expr); //从expr中推导T和ParamType
基本原则:
(1)一般情况下,expr 与 T 的类型是一致的,T 一般不为引用,除非在下述第二种情况下;
(2)在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略
1.1.1 ParamType 是一个指针或引用,但不是通用引用
(1)ParamType 是 引用
- 如果 expr 的类型是一个引用,忽略引用部分
- 然后 expr 的类型与 ParamType 进行模式匹配来决定 T (相同的会消掉)
expr 若是指针,则仍按基本规则匹配
void f(const T& param); 这里 const 表明的是 T 本身是不可变的,若传递为 int* 指针,param 类型为 int (*const)&
(2)ParamType 是 指针,与上述类似
- 如果 expr 的类型是一个指针,忽略指针部分
- 与上述类似 。。。
expr 不能是引用;
1.1.2 ParamType 是一个通用引用
-
如果 expr 是左值,T 和 ParamType 都会被推导为左值引用。这非常不寻常,第一,这是模板类型推导中唯一一种T被推导为引用的情况。第二,虽然 ParamType 被声明为右值引用类型,但是最后推导的结果是左值引用。
-
如果 expr 是右值,就使用基本的推导规则
1.1.3 ParamType 既不是指针也不是引用
按通过传值(pass-by-value)的方式处理
- 如果 expr 的类型是一个引用,忽略这个引用部分;忽略 对象自身的 const/ volatile
- 如果 expr 的类型是一个指针,按基本规则;
1.1.4 数组和函数实参
数组和函数实参会退化为指针
void func(const char (param)[13]) ;
等价于 void func(const char *param) ;void func(const char (¶m)[13]); 可以接受指向数组的引用,必须得 const char[13] 数组类型才能调用
有指向函数的引用;
template<typename T>
void f2(T & param); //传引用给f2
f2(someFunc); //param被推导为指向函数的引用,//类型是void(&)(int, double)
2. auto类型推导
(1)auto类型推导 与 模板类型推导基本一致;可将 auto 视为 T,类型说明符视为 ParamType ,等号右边值视为实参;
const auto & x = 27; 类型说明符为 const auto &
(2)特例:使用大括号初始化的变量,auto 推导出的类型为 std::initializer_list
( 大括号里面必须是相同类型的变量),模板无法推导这种情况,除非显式写为
template<typename T>
void f(std::initializer_list<T> initList);auto a{42}; C++17 现在推导出为 int
auto a{42, 17}; C++17 中为 error
auto c = {42}; // still initializes a std::initializer_list<int>
(3)auto 可用于函数返回值或 lambda 表达式中的形参,此时其完全等价于 模板类型推导
lambda 表达式的返回值是通过 auto 规则推导。此时其完全等价于 模板类型推导
(4)lambda 中初始化捕获器中的类型推导也采用了 auto 规则,此时是真正的 auto 推导;
初始化捕获器中实际上声明了新的变量;
特列:[&] 代表引用捕获,需要注意引用变量的生命期要比 lambda 长,否则造成悬挂指针的危险;
3. decltype
尾置返回类型的好处是我们可以在函数返回类型中使用函数形参相关的信息(这种情况下不依赖 auto 推导)
decltype 应用于变量名,只是简单返回变量的声明类型;
decltype 应用于左值表达式,返回相应左值引用;
语法 decltype(auto)
表示需要推导出类型,但是它使用 decltype 的规则进行推导