CppCoreGuidelines P.4 理想情况下,程序应该要静态类型安全
21 December 2021
P.4 Ideally, a program should be statically type safe
理由
理想情况下,我们写程序要能做到完全的静态(编译期)类型安全。但是,在实际开发工作中,我们也避免不了会出现动态类型的情况。比如以下这些情况:
- 联合体( union ):我们建议使用C++17的
variant
来替代联合体 - 强制类型转换( cast ): 我们建议少用强制类型转换,多用模板
- 数组衰变(array decay):使用 GSL 的
span
避免 - 范围越界错误(range error):使用
span
避免 - 变窄类型转换(narrowing conversion):减少出现变窄类型转换的情况,如果不能避免,建议使用 GSL 中的
narrow_cast
variant
variant
是C++17增加的一个功能,用以作为 union
的安全替代。它可以保存模板参数列表中的某一类型的值。
//#include <variant> variant<int, double, string> var1{1}; cout << "index is: " << var1.index() << endl; cout << "var1 is int now: " << get<0>(var1) << endl; var1 = 1.2; cout << "index is: " << var1.index() << endl; cout << "var1 is double now: " << get<1>(var1) << endl; var1 = "关注元宇宙物联网公众号"; cout << "index is: " << var1.index() << endl; cout << "var1 is string now: \n" << get<2>(var1) << endl;
index is: 0 var1 is int now: 1 index is: 1 var1 is double now: 1.2 index is: 2 var1 is string now: 关注元宇宙物联网公众号
数组衰变
所谓的数组衰变是指在使用数组的过程中,数组元素类型和长度信息丢失的情况。比如当我们以指针或数值的形式传递数组参数给函数的时候,数组的长度信息就丢失了。
void display_array_from_value(int *p) { cout << "以数值形式传递的数组参数,计算的内存占用值为:"; cout << sizeof(p) << endl; } void display_array_from_pointer(int (*p)[10]) { cout << "以指针形式传递的数组参数,计算的内存占用值为:"; cout << sizeof(p) << endl; } int main() { int arr[10] = {1, 2, }; cout << "数组实际占用内存空间是:"; cout << sizeof(arr) << endl; display_array_from_value(arr); display_array_from_pointer(&arr); return 0; }
数组实际占用内存空间是:40 以数值形式传递的数组参数,计算的内存占用值为:8 以指针形式传递的数组参数,计算的内存占用值为:8
为了避免数组衰变:
- 可以以指针或数值传递数组参数的同时,传一个数组长度给函数,然后不用
sizeof
- 或者以引用形式传递数组参数
- 使用 GSL 的
span
void display_array_from_value_with_size(int *p, size_t sz) { cout << "通过额外参数传入的内存占用值:"; cout << sz << endl; } void display_array_from_reference(int (&p)[10]) { cout << "以引用形式传递的数组参数,计算内存占用值为:"; cout << sizeof(p) << endl; } void display_array_from_span(gsl::span<int> p) { for(const auto v: p) { cout << v << " "; } } int main() { int arr[10] = {1, 2, }; cout << "数组实际占用内存空间是:"; cout << sizeof(arr) << endl; display_array_from_value_with_size(arr, sizeof(arr)); display_array_from_reference(arr); display_array_from_span(arr); return 0; }
数组实际占用内存空间是:40 通过额外参数传入的内存占用值:40 以引用形式传递的数组参数,计算内存占用值为:40 1 2 0 0 0 0 0 0 0 0
注意,添加 gsl 的方式:
git clone git@github.com:microsoft/GSL.git cp GSL/include/gsl /ucrt64/include
范围错误
void access_out_of_range(int *p) { cout << p[3] << endl; } void pass_span(gsl::span<int> p) { cout << p[1] <<endl; } int main() { int arr[2] = {1, 2}; access_out_of_range(arr); pass_span(arr); return 0; }
260653 2
这里打印出来的第一个值已经超出数组的范围了,所以是内存当前的一个不确定的数据。
收缩转换
gsl::narrow
在收缩转换出错的时候,会异常。
int var1 = -42; cout << (unsigned int)var1 << endl; cout << static_cast<unsigned int>(var1) << endl; char var2 = 10; cout << (unsigned int)var2 << endl; cout << static_cast<unsigned int>(var2) << endl;
4294967254 4294967254 10 10
int var1 = -42; cout << gsl::narrow_cast<unsigned int>(var1) << endl; try { cout << gsl::narrow<unsigned int>(var1) << endl; } catch (gsl::narrowing_error &e) { cout << e.what() << endl; } char var2 = 10; cout << gsl::narrow_cast<unsigned int>(var2) << endl; try { cout << gsl::narrow<unsigned int>(var2) << endl; } catch (gsl::narrowing_error &e) { cout << e.what() << endl; }
4294967254 narrowing_error 10 10