22 May 2023

C++ 核心指南目录

“Use signed types for arithmetic”

理由

因为大部分算术运算默认是有符号类型的。 当 y > xx - y 会产生负数。除了某些特殊情况,比如求模运算,可能需要无符号类型。

例子

无符号类型的算术运算可能会导致你不期望的结果。尤其是混用了有符号与无符号类型的运算:

template<typename T, typename T2>
T subtract(T x, T2 y)
{
    return x - y;
}

void test()
{
    int s = 5;
    unsigned int us = 5;
    cout << subtract(s, 7) << '\n';       // -2
    cout << subtract(us, 7u) << '\n';     // 4294967294
    cout << subtract(s, 7u) << '\n';      // -2
    cout << subtract(us, 7) << '\n';      // 4294967294
    cout << subtract(s, us + 2) << '\n';  // -2
    cout << subtract(us, s + 2) << '\n';  // 4294967294
}
int main()
{
    test();
    return EXIT_SUCCESS;
}
-2
4294967294
-2
4294967294
-2
4294967294

这里,我们很清楚发生了什么。但是如果写成 us - (s + 2) 或者 s += 2; ...; us - s ,你很可能就不知道为什么结果会是 4294967294。

例外

如果确实需要求模运算,可以用无符号类型。但是注意写明注释,尤其当可能产生一些数值溢出行为的时候。这种代码会让程序员感到意外。

例子

标准库使用无符号类型作为下标。而内置数组类型则用有符号类型作为下标。这方面产生惊讶和 bug 在所难免:

int a[10];
for (int i = 0; i < 10; ++i) a[i] = i;
vector<int> v(10);
// compares signed to unsigned; some compilers warn, but we should not
for (gsl::index i = 0; i < v.size(); ++i) v[i] = i;

int a2[-2];         // error: negative size

// OK, but the number of ints (4294967294) is so large that we should
// get an exception
vector<int> v2(-2);

gsl::index 做下标。

强化

  • 标记混用有符号和无符号类型的算术计算
  • 标记无符号类型的计算结果复制给有符号类型或打印成有符号类型
  • 标记用负数作为容器下标的地方
  • 不要标记有符号类型与无符号类型混合比值的地方,不然会有太多噪音。比如:其中一个参数是 sizeof 或容器的 .size() ,而另一个参数是 ptrdiff_t