另一方面...
迷思1:std::size_t
是无符号的,是因为不再适用的遗留限制。
这里通常有两个“历史”原因:
sizeof
返回std::size_t
,自C语言时代以来就一直是无符号的。
- 处理器的字长较小,因此重要的是挤出额外的范围。
但是,尽管这些原因非常古老,但它们实际上并没有被归类为历史。
sizeof
仍然返回一个无符号的std::size_t
。如果您想与sizeof
或标准库容器进行交互操作,您将不得不使用std::size_t
。
替代方案都更糟:您可以禁用有符号/无符号比较警告和大小转换警告,并希望值始终在重叠范围内,以便您可以忽略使用不同类型可能引入的潜在错误。或者您可以进行大量的范围检查和显式转换。或者您可以引入自己的大小类型,并具有聪明的内置转换来集中范围检查,但是没有其他库将使用您的大小类型。
虽然大多数主流计算都在32位和64位处理器上完成,但C ++仍然在嵌入式系统中使用16位微处理器。在这些微处理器上,拥有一个可以表示内存空间中任何值的字大小值通常非常有用。
我们的新代码仍然必须与标准库进行交互。如果我们的新代码使用有符号类型,而标准库继续使用无符号类型,那么每个必须同时使用两者的消费者都会变得更加困难。
神话2:您不需要那个额外的位。(也就是说,当您的地址空间只有4GB时,您永远不会拥有大于2GB的字符串。)
大小和索引不仅仅是为了内存。您的地址空间可能有限,但您可能会处理比您的地址空间大得多的文件。虽然您可能没有一个超过2GB的字符串,但您可以轻松地拥有一个超过2G位的位集。而且不要忘记为稀疏数据设计的虚拟容器。
迷思3:您总是可以使用更宽的有符号类型。
并非总是如此。对于一个或两个局部变量,您可以使用std::int64_t
(假设您的系统有一个)或signed long long
,并且可能编写完全合理的代码。(但您仍然需要一些显式转换和两倍的边界检查,否则您将不得不禁用一些编译器警告,这可能会提醒您在代码其他地方存在错误。)
但是如果您正在构建一个大型索引表,怎么办?当您只需要一个位时,您真的想要每个索引额外两个或四个字节吗?即使您有足够的内存和现代处理器,使该表扩大一倍可能会对参考位置产生不良影响,并且所有范围检查现在都是两步,降低了分支预测的有效性。而如果您没有那么多内存呢?
神话4:无符号算术是令人惊讶和不自然的。
这意味着有符号算术并不令人惊讶或者更自然。也许在数学方面思考时,所有基本算术运算都在所有整数集合上关闭。
但是我们的计算机不使用整数。它们使用整数的无穷小部分。我们的有符号算术不在所有整数集合上关闭。我们有溢出和下溢。对于许多人来说,这是如此令人惊讶和不自然,他们大多数时候都忽略了它。
这是一个错误:
auto mid = (min + max) / 2; // BUGGY
如果min和max是有符号的,相加可能会溢出,从而导致未定义行为。我们大多数人经常忽略这种错误,因为我们忘记了加法不适用于有符号整数集合。我们之所以能够继续使用它,是因为我们的编译器通常会生成一些合理的代码(但仍然令人惊讶)。
如果min和max是无符号的,相加仍然可能溢出,但是未定义行为消失了。你仍然会得到错误的答案,所以仍然令人惊讶,但不会比有符号整数更令人惊讶。
真正令人惊讶的无符号操作是减法:如果你从一个较小的无符号整数中减去一个较大的无符号整数,你将得到一个很大的数字。这个结果与除以0一样令人惊讶。
即使你能够在所有的API中消除无符号类型,如果你处理标准容器、文件格式或网络协议,你仍然必须准备好应对这些无符号的“惊喜”。值得为解决问题的一部分而给你的API增加摩擦吗?