CppCoreGuidelines 二周目笔记 01 (完结)
原话: One way of thinking about these guidelines is as a specification for tools that happens to be readable by humans.
我的观点是不要死记硬背,最简单的方法是把 clang-tidy 之类的工具集成到你的开发环境里
重读一遍 Cpp Core Guidelines ,记一些我喜欢的条目,不保证我的理解百分百正确。
00. 优先用标准库的工具(ES.1)
家人们谁懂啊, Standard Library 的工具真的好用到跺 jiojio ,咱就是说怎么还有人写这种代码:
1 | double sum = 0.0; |
真虾头,前几天去星巴克 gap hours 的时候看到一个集美这样写:
1 | auto sum = std::accumulate(std::begin(v), std::end(v), 0.0); |
啊啊啊啊啊啊啊啊一整个爱住,那么多 std:: 真的绝绝子,还有那个 auto 一下子击中了咱的心巴,家人们狠狠地爱上了,咱不允许世界上还有人不知道这个方法。
01. 更先进的 if 语句(ES.6)
C++17 之后 if 里面也可以有初始化语句,比如这样的逻辑:
1 | Json::Value some_config = config["some"]; |
some_config 已经完成了它的使命,但它还没有离开它的作用域,如果后面还要解析更多配置的话就会有很多 Json::Value 漏在外面很难看,如果有 C++17 可以改成:
1 | if (Json::Value some_config = config["some"]; some_config.isUInt()) { |
非常干净, some_config 只在 if 里存活。
02. 不要把变量的声明和使用隔开很远(ES.22, NR.1)
很多 C 库函数都是开头一大堆变量(各种 struct 指针),然后隔了 114514 行才用到,这个时候没有 LSP 根本知不道这个变量到底是什么类型的。不是否认这些 C 库的优秀,只是我和 BS 的观点一致
不要这样:
1 | void foo() { |
03. 一个变量只用来完成一个逻辑(ES.26)
比如这种代码存在可读性问题:
1 | void foo() { |
04. 为复杂的初始化(尤其是 const 变量)使用 lambda(ES.28)
其实我经常用 lambda 来初始化静态常量,只是以前不知道这么做有没有问题,现在有 BS 认可辣
比如这种:
1 | Data d; // 先默认初始化 |
可以改成:
1 | const Data d{[&] { |
05. 真的需要在 switch 中直落到下一个 case 的情况下使用 [[fallthrough]] 标注(ES.78)
1 | switch (reason) { |
06. 不要在原生的 for 循环里修改循环控制变量
这种写法非常容易出错(我也经历过很多次因为这么写出现错误的情况):
1 | for (int i = 0; i < 114514; i++) { |
可以改成:
1 | for (bool skip = false; int i = 0; i < 114514; i++) { |
07. 「疑问」使用 gsl::index 与 size_t 混用是正确的
众所周知 C++ 的标准库的容器使用无符号作下标,一般 size() 返回值类型 size_type 就是 size_t 的 typedef (实际上在 X86-64 上就是 uint64_t ), CppCoreGuidelines 原文很多地方都出现了类似这种写法:
1 | gsl::index i = 0; |
我以为 gsl::index 能整出什么花活能避免 size_t 转换成有符号类型时的溢出( int128_t ?我不好说),结果 gsl::index 就是 ptrdiff_t (实际上在 X86-64 上就是 int64_t ),问题根本没有得到解决,然后 CppCoreGuidelines 写了这样一条建议:
(To avoid noise) Do not flag on a mixed signed/unsigned comparison where one of the arguments is
sizeofor a call to container.size()and the other isptrdiff_t.
大致意思就是 gsl::index 和 uint64_t 混用不要报错,而其他类型(比如 int64_t )与 uint64_t 混用却建议报错。意义不明,可能 ptrdiff_t 有什么我不知道的黑魔法?希望知道真相的大佬指点一下。
08. 进行可预测的内存访问(Per.19)
优先访问相邻数据(线性的)是更利于缓存算法的,比如这种写法:
1 | constexpr const int ROWS = 114514; |
最好换成这样:
1 | constexpr const int ROWS = 114514; |
效率一般更高。
09. 「嘲讽」建议开发时假设自己的代码会用在多线程里
你说的对,请问标准库什么时候支持多线程?
10. #pragma once 并不是标准(SF.9)
虽然我一直都在用 Define Guard (老壁灯)而不是 #pragma once ,但没想到 BS 说这种写法是厂商扩展:
1 |
建议用传统的 Define Guard :
1 |
|
11. 不要没活硬整乱放 const (NL.26)
1 | const int i = 0; // 挺好的 |
完结
感觉整体读下来帮助不是很大,因为几年前从 M$ 的 VS 逃到 Neovim + LSP 之后我就一直在用 clang-tidy ,很多习惯都是正确的。当然还有很多错误处理、并发并行之类的内容,我只是粗略看了一遍,因为 CppCoreGuidelines 写的也很粗略所以没有做记录。