# 【CCG】理念
作者:wallace-lai
发布:2024-02-25
更新:2024-02-25
## 理念
理念性规则强调了一般性,因此,无法进行检查。不过,理念性的规则为后续的具体规则提供了理论依据。
### 规则1:在代码中直接表达思想
程序员应该直接用代码表达他们的思想,因为代码可以被编译器和工具检查。
```cpp
class Data {
// ...
public:
Month month() const; // 这样做
int month(); // 不要这样做
// ...
};
```
上述的案例中存在的问题有:
(1)month不应该修改日期对象,所以应该加上const,但这一点的重要性经常被新手忽略;
(2)month应该是返回一个Month对象,尽管其可能是int型的枚举值,但你应该使用Month来表意;
上述两个问题是一些糟糕C++代码中的常客,大量出现在新手程序员写出的代码中。
```cpp
// 不要这样做
int index = -1;
for (int i = 0; i < v.size(); i++) {
if (v[i] == val) {
index = i;
break;
}
}
// 这样做
auto iter = std::find(begin(v), end(v), val);
```
一个专业的C++程序员应该了解STL算法。使用它们,可以显式地避免循环的使用。**如果你显式地使用循环,说明你不了解STL算法**。
### 规则2:使用ISO标准C++写代码
要得到一个可移植的C++程序,尽量使用当前的C++标准,不要使用编译器扩展。此外还需要注意**未定义行为**和**实现定义行为**。
### 规则3:表达意图
选择合适的循环方式来表达意图,比如下面的案例:
```cpp
for (const auto & v : vec) { /* ... */ } // (1)
for (auto &v : vec) { /* ... */ } // (2)
std::for_each(std::execution::par, vec, [](auto v) { /* ... */ }); // (3)
```
(1)循环(1)并不修改容器vec中的元素;
(2)循环(2)可能会修改容器vec中的元素;
(3)循环(3)以并行的方式执行,这意味着我们并不关心以何种顺序处理元素
### 规则4:理想情况下,程序应该是静态类型安全的
C++是一种静态类型语言,这意味着编译器知道数据的类型,因此编译器可以检测到类型错误。但是,存在以下几种情况导致类型不安全问题出现:
(1)联合体;
可以使用C++ 17中的`std::variant`代替联合体
(2)转型cast;
基于模板的泛型可以减少转型的需要。
(3)数组退化;
都使用C++了,就不要再用C语言的数组了
(4)范围错误;
(5)窄化转换;
窄化转换是对算术值的有精度损失的隐式转换,推荐使用`{}`初始化语法,这样编译器可以检测到窄化转换。
```cpp
int i1(3.14);
int i2 = 3.14;
int i3 {3.14};
int i4 = { 3.14 };
```
### 规则5:编译期检查优于运行期检查
如果可以在编译期进行检查,那就应该在编译期检查。从C++ 11开始,该语言就支持`static_assert`了。
如果在static_assert中放只能在运行期检查的表达式会怎样呢?
### 规则6:不能在编译期检查的事项应该在运行期检查 ### 规则7:尽早识别运行时错误 ### 规则8:不要泄露任何资源 处理资源的惯用手法是RAII,本质上是在用户类型的构造函数中获取资源,在析构函数中释放资源。通过使对象成为一个有作用域的对象,让C++自动地照顾资源的生命周期。 ### 规则9:不要浪费时间和空间 节约时间和空间是一种美德。 ```cpp void lower(std::string s) { for (unsigned int i = 0; i <= std::strlen(s.data()); i++) { s[i] = std::tolower(s[i]); } } ``` 不如使用: ```cpp std::transform(s.begin(), s.end(), [](char c) { return std::tolower(c); }); ``` 下面案例中的问题更加隐蔽:为一个用户定义的数据类型声明拷贝语义(拷贝构造函数和拷贝赋值运算符)会抑制自动定义的移动语义(移动构造函数和移动赋值运算符)。最终,编译器无法使用相对更加高效的移动语义,即使是在移动可行的场景下。 ```cpp struct S { std::string s_; S(std::string s) : s_(s) {} S(const S &rhs) : s_(rhs.s_) {} S& operator=(const S &rhs) { s_ = rhs.s_; return *this; } }; S s1; S s2 = std::move(s1); // 进行拷贝,不能移动 ``` ### 规则10:不可变数据优于可变数据 使用不可变数据的好处很多: (1)当你使用常量时,你的代码很容易验证; (2)常量具有更好的优化潜力; (3)常量在并发程序中有很大的优势,不可变数据不存在数据竞争; ### 规则11:封装杂乱的构构件,不要让它在代码中散步开 混乱的代码往往是低级的代码,容易隐藏错误,容易出问题。要么用STL中的构件来取代你杂乱的代码;要么把这些杂乱的代码封装到一个自定义的类型中去。 ### 规则12:适当使用辅助工具 枯燥和重复性的工作尽量让计算机去完成。 ### 规则13:适当使用支持库 你应该找设计良好、文档齐全、支持良好的库。比如,C++标准库、Boost库等。