C++核心指南——C++创始人参与编写的代码指南笔记及翻译
- P:Philosophy
-
P.1 Express ideas directly in code 直接用代码表达思想
不直接使用语言的原始数据类型名,变量的类型名应该与变量的含义相关
Month month() const; // Good! int month(); // Bad!
优先使用设计良好库来代替原始的语句,表达更加清楚的意图
- for循环的意图是查找一个元素
//BAD void f(vector<string>& v) { string val; cin >> val; // ... int index = -1; // bad, plus should use gsl::index for (int i = 0; i < v.size(); ++i) { if (v[i] == val) { index = i; break; } } // ... }
- 更好的表达:使用一个find函数来表达查找的意图
//GOOD void f(vector<string>& v) { string val; cin >> val; // ... auto p = find(begin(v), end(v), val); // better // ... }
-
P.2 Write in ISO Standard C++ 使用C++标准编写代码
-
P.3 Express intent 表达意图
除了在变量类型中表达意图外,在代码中也要体现精确的意图
-
遍历容器中的元素的操作
-
BAD:使用意义不明的while循环和超出循环生命期的i变量
gsl::index i = 0; while (i < v.size()) { // ... do something with v[i] ... }
-
BATTER:使用范围for,明确表达遍历意图
// 不修改元素版本 for (const auto& x : v) { /* do something with the value of x */ } // 修改元素版本 for (auto& x : v) { /* modify x */ }
-
BATTER:使用一个命名算法,能够更加明确表达遍历的意图,且还包含对容器中元素顺序不感兴趣
for_each(v, [](int x) { /* do something with the value of x */ }); for_each(par, v, [](int x) { /* do something with the value of x */ });
-
-
Note
- Alternative formulation: Say what should be done, rather than just how it should be done.
- 替换表达:说做什么而不是怎么做。
- Some language constructs express intent better than others.
- 有些语言结构比其他语言结构更好地表达意图。
-
-
P.4 Ideally, a program should be statically type safe 理想情况下程序应该是静态类型安全的
- 类型安全:对于程序的类型安全来说,不会出现类型错误就是类型安全
- 静态类型安全:代码编写过程中保证类型是安全,在编写代码时及时检查类型
- 理想情况下,程序应该保证完全类型安全,包括静态和编译期类型安全,但是这几乎做不到,所以退而求其次,保证静态类型安全
- 容易出现类型不安全的情况
- unions 联合体
- casts 类型转换
- array decay 数组退化
- range errors 范围错误
- norrowing conversions 窄化类型转换
- 类型不安全解决办法
- union:使用 variant(C++17) 代替
- casts:尽量不使用类型转换,适当用模板功能替换
- array decay:使用span来访问数组
- range errors:使用span来访问数组
- norrowing conversions:尽量不使用窄化类型转换,必要时候用narrow_cast替代
-
P.5 Prefer compile-time checking to run-time checking 选择编译时检查而不是运行时检查
-
不要把在编译期能够完成的事情留到运行期做‘
-
类型检查
-
BAD:在运行期进行类型检查
// Int是整数类型的一个别名 // 代码段的功能是确保Int类型的大小大于4字节 int bits = 0; // 这种写法,不可避免的额外变量 for (Int i = 1; i; i <<= 1) ++bits; if (bits < 32) cerr << "Int too small\\n";
-
BATTER:使用编译期断言来进行类型检查
// Int是int类型的一个别名 static_assert(sizeof(Int) >= 4); // 编译期检查
-
-
数组大小检查
-
BAD:运行时指定数组大小,又可能会超范围
void read(int* p, int n); // 从p中读取最多的n个数 int a[100]; read(a, 1000); // 提供的n大于数组范围,导致数组越界
-
BATTER:编译器计算数组大小
void read(span<int> r); // 读入整数r的范围 int a[100]; read(a); // 让编译器去计算数组长度
-
-
-
P.6 What cannot be checked at compile-time should be checked at run-time 无法在编译期检查的问题需要在运行期检查
- 对于程序中可能出现的错误都需要进行检查,难以检测的错误会导致程序的崩溃和出现错误结果
- 理想情况下,应该捕获所有错误,但是实际根做不到。不过函数应该尽可能捕获能捕获的错误
-
P.7 Catch run-time errors early 尽量早地捕获运行期错误
-
在数组越界访问前检查访问范围
-
BAD:m可能越界,但是直到访问到p[10]时才会被发现
void increment1(int* p, int n) // bad: error-prone { for (int i = 0; i < n; ++i) ++p[i]; } void use1(int m) { const int n = 10; int a[n] = {}; // ... increment1(a, m); // maybe typo, maybe m <= n is supposed // but assume that m == 20 // ... }
-
BATTER:在访问前会检查范围,在访问数组元素前就会出错
void increment2(span<int> p) { for (int& x : p) ++x; } void use2(int m) { const int n = 10; int a[n] = {}; // ... increment2({a, m}); // maybe typo, maybe m <= n is supposed // ... }
-
-
Enforcement
- Look at pointers and arrays: Do range-checking early and not repeatedly
- 对于指针和数组:尽早进行范围检查且避免见检查重复
- Look at conversions: Eliminate or mark narrowing conversions
- 对于转换:剔除或者明确标记窄化转换
- Look for unchecked values coming from input
- 注意从输入带来的未检查值
- Look for structured data (objects of classes with invariants) being converted into strings
- 注意被转换为strings的结构化数据
-
-
P.8 Don’t leak any resources 不要泄漏任何资源
-
leak:anything that isn't cleaned up
-
打开文件
-
BAD:直接使用文件结构体
void f(char* name) { FILE* input = fopen(name, "r"); // ... if (something) return; // bad: if something == true, a file handle is leaked // ... fclose(input); }
-
BETTER:使用文件流对象,RAII
void f(char* name) { ifstream input {name}; // ... if (something) return; // OK: no leak // ... }
-
-
使用RAII机制,利用局部变量的隐式析构作为资源管理方法
-
Enforcement
- 使用智能指针管理资源
- 不直接使用new 和delete
- 注意直接返回原始指针的函数,如fopen、malloc
-
-
P.9 Don’t waste time or space 不要浪费时间和空间
- 在C++中投入时间优化时间和空间是值得的
-
P.10 Prefer immutable data to mutable data 选择不可变数据而不是可变数据
常数要比变量更容易优化,且不存在对于常数的竞争(多线程)
-
P.11 Encapsulate messy constructs, rather than spreading through the code 封装杂乱的构造,而不是在代码中扩展
-
在能够使用库的地方尽可能使用库
-
BAD
int sz = 100; int* p = (int*) malloc(sizeof(int) * sz); int count = 0; // ... for (;;) { // ... read an int into x, exit loop if end of file is reached ... // ... check that x is valid ... if (count == sz) p = (int*) realloc(p, sizeof(int) * sz * 2); p[count++] = x; // ... }
-
BETTER:
vector<int> v; v.reserve(100); // ... for (int x; cin >> x; ) { // ... check that x is valid ... v.push_back(x); }
-
-
P.12 Use supporting tools as appropriate 适当地使用支持工具
- Static analysis tools 静态分析工具
- concurrency tools 并发工具
- testing tool 测试工具
-
P.13 Use support libraries as appropriate 适当地使用支持库
-