26 March 2022

C++ 核心指南目录

理由:

用数组指针+数组大小作为参数的接口很容易出错。指向数组的指针必须依靠其他途径传递其大小。

举例:

void copy_n(const T* p, T* q, int n); // copy from [p:p+n) to [q:q+n)

如果 q 的元素数少于 n 会怎么样?我们会覆盖一些无关的内存。如果 p 的长度小于 n 会怎么样?我们可能复制了一些无关的内存。任何一种情况,都导致了未定义的行为,可能是很难处理的 bug。

void my_copy_n(const char* p, char* q, int n) {
    for (; n>0 ; n--) {
        *q = *p; p++; q++;
    }
}
int main()
{
    char pp[] = "hello";
    char qq[] = "hel";
    cout << qq << endl;
    my_copy_n(pp, qq, 5);
    cout << qq << endl;

    char pp1[] = "hel";
    char qq1[] = "hello";
    cout << qq1 << endl;
    my_copy_n(pp1, qq1, 5);
    cout << qq1;
    return 0;
}
hel
helloello
hello
hel

替代方案:

考虑显式地用 span

void copy(span<const T> r, span<T> r2); // copy r to r2

反例:

void draw(Shape* p, int n);  // poor interface; poor code
Circle arr[10];
// ...
draw(arr, 10);

给参数 n 的值如果是 10 可能会出错:C语言里数组下标的不成文的习俗是 [0:n) 即包括 0,不包括 n。更坏的情况是, 调用 draw() 函数在编译时没有问题,数组隐式的转换成了指针(数组衰减),另一个隐式转换: Circle 转成了 Shapedraw() 函数无法逐个访问数组的元素,因为无从知道元素本身的大小( ShapeCircle 大小不一)。

替代方案:

使用支持类,确保元素数量可知,避免危险的隐式转换。

比如:

void draw2(span<Circle>);
Circle arr[10];
// ...
draw2(span<Circle>(arr));  // deduce the number of elements
draw2(arr);    // deduce the element type and array size

void draw3(span<Shape>);
draw3(arr);    // error: cannot convert Circle[10] to span<Shape>

draw2() 得到与 draw() 同样数量的信息。但是不在会隐式转换 Circle 类型。

例外:

使用 zstringczstring 表示 C 风格的 \0 结尾的字符串。这时候,要用 GSL 的 std::string_viewspan<char> 避免错误的范围。

强化:

  • (简单)隐式的将数组类型转换成指针类型的地方,报警。允许例外情况: zstringczstring 指针。
  • (简单)指针类型的数值计算,报警。允许例外情况: zstringczstring 指针。