26 March 2022

C++ 核心指南目录

I.13: Do not pass an array as a single pointer

理由

接口的参数如果是用数组指针+数组大小的方式传递,很容易出错。我们只能依靠其他途径获取指针所指的数组的大小。

例子

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 指针。