21 December 2021

C++ 核心指南目录

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