20 December 2021

C++ 核心指南目录

P.3 Express intent

理由

如果要读代码的注释才能理解代码的意图,那可能就意味着,你写的代码不够清晰,你没有很明确地表达出代码的意图。

例子

#+attr_html :class bad

vector<int> v = {1, 2, 3, 4, 5};
gsl::index i = 0;
while (i < v.size()) {
    cout << v[i] << " ";
    i++;
}
1 2 3 4 5

这里,我们的意图其实就是要循环一遍向量 v ,把它的元素的值一个个打印出来。但是呢,读别人拿到这个代码,可能就无法一眼看出来代码要做的事情。并且,代码里还多余的定义了一个索引变量 i ,这个变量的作用域范围还超出了循环体。

倘若把 while 循环改成以下 for 循环,就比较直截了当了:

vector<int> v = {1, 2, 3, 4, 5};
for (const auto& x : v) {
    cout << x << " ";
}
1 2 3 4 5

并且,在这里,我们还用了 const 关键字修饰 x ,我们限定了,在这个循环里,我们不会修改 x 的值。如果不小心在循环中对 x 的值进行修改,编译器就会报错。那么,根据编译出错信息,我们就比较容易在编码早期发现问题了。

例如,我们在循环体内,做一个自增操作,就收到了编译器的警告:

vector<int> v = {1, 2, 3, 4, 5};
for (const auto& x : v) {
    cout << ++x << " ";
}
C-src-eO7Jst.cpp: In function 'int main()':
C-src-eO7Jst.cpp:14:15: error: increment of read-only reference 'x'
   14 |     cout << ++x << " ";
      |               ^

如果我们想要明确表明,我们要在循环中修改变量 x ,那么就可以去掉 const

vector<int> v = {1, 2, 3, 4, 5};
for (auto& x : v) {
    cout << ++x << " ";
}
2 3 4 5 6

除了 for 循环,我们也可以用std::ranges::for_each更清晰的表达程序意图:

vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
std::ranges::for_each(v, [](const auto &x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0

需要注意的是,在 algorithm 中,也有一个std::for_each,它的前两个参数是 InputIterator

vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
for_each(begin(v), end(v), [](int x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0

如果,在循环访问的时候,不需要按照顺序一个一个访问,可以添加 execute::par 参数,来表示并行访问。因为在这里 v 的元素不够多,不会有太明显的变化:

vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
for_each(execution::par, v.begin(), v.end(), [](int x) { cout << x << " "; });
1 2 3 4 5 6 7 8 9 0

另外举一个例子,如果要表示在两点间画线,以下两种写法里,第二种就比较明确:

#+attr_html :class bad

draw_line(int, int, int, int);  // obscure
draw_line(Point, Point);        // clearer

一个优秀的程序员应该:

  • 熟悉掌握指南支持的程序库(guidelines support library, gsl)
  • 熟悉了解ISO C++标准库
  • 其他项目中使用的基础库

强化

  • 弄清楚简单的 for 循环和带范围的for_each的差异,什么时候可以选用 for_each
  • 了解f(T*, int)接口和f(span<T>)接口的差异
  • 避免只在循环中用到的变量,不要超出循环体范围
  • 避免直接用 newdelete 的操作
  • 注意有过多内置类型的函数参数,通常可以通过自定义类型减少参数数量