数组索引类型:有符号/无符号整数的优势

12

在C++中,数组索引的默认大小是size_t,在大多数x86-64平台上它是一个64位的无符号整数。我正在构建自己的std::vector类用于我的高性能计算库(其中一个主要原因是我希望这个类能够占据指针的所有权,而std::vector没有提供这个功能)。对于数组索引的类型,我考虑使用以下两种之一:

  • size_t
  • 我的自定义index_t类型,它将是一个有符号的int或long signed int,取决于我的程序

相对于无符号整数来说,使用有符号整数的优点很多,比如

for (index_t i = 0; i < v.size() - 1; ++i)

按照预期工作(使用无符号整数时,如果v的大小为0,则此循环会变得疯狂)

for (index_t i = v.size() - 1; i >= 0; --i)

它的表现如预期一样,并有许多其他优点。就性能而言,它甚至似乎比原来更好一些。

a + 1 < b + 1

当使用带符号整数时,a < b 可以被缩小到一个更小的值(溢出是未定义的),而无符号整数不行。性能方面唯一的优势似乎是对于无符号整数,a /= 2可以缩减为位移操作,但对于带符号整数则不行。

我想知道C++委员会为什么决定将size_t用作无符号整数,因为它似乎引入了很多麻烦,而只有很少的优势。


与您的想法不同,无符号类型在进行算术运算时非常有效,也适用于倒数索引。如果您按范围思考,这将非常好,i < v.size() 在这里会起作用。否则,如果您想要一个有符号类型来进行这样的操作,请选择 ptrdiff_t,这是正确的类型,用于编码差异。也许您会发现这很有趣,它主要涉及 C 语言,但在这里两者紧密联系。http://gustedt.wordpress.com/2013/07/15/a-praise-of-size_t-and-other-unsigned-types/. 噢,还有投票关闭,因为这主要是基于个人观点的。 - Jens Gustedt
@Gustedt:感谢提供这个有趣的链接。我试图将主题集中在事实而非观点上。一些人帮助我考虑使用ptrdiff_t,这很有用。 - InsideLoop
2个回答

8
在标准中使用无符号类型作为索引或大小的动机只基于仅适用于16位计算机的约束。在C ++中,任何整数类型的“自然”类型都是int,并且应该使用它; 正如您已经注意到的,尝试在C ++中使用无符号类型作为数字值会导致问题。如果您担心大小超过int,则ptrdiff_t将是合适的; 毕竟,这是指针或迭代器相减的结果类型。(v.size()的类型与v.end()-v.begin()的类型不同实际上是标准库中的设计缺陷。)

1
v.size() 和 v.end() - v.begin() 对我来说也很奇怪。感谢您提供的历史原因和16位机器的提示。 - InsideLoop
我最终决定使用自己的类型,称为index_t,它将是一个有符号整数。我主要会使用int或ptrdiff_t。当我想要使用大量的index_t数组并且希望节省内存带宽时,int会更好;而当我想要使用非常大的数组时,ptrdiff_t会很有用。 - InsideLoop
1
但是,使用有符号类型会导致每次将它们与标准库容器(如std::vector)一起使用时触发符号转换和符号比较警告(例如用于索引或与.size()进行比较)。您只是禁用这些警告还是每次都进行static_cast?后者对我来说似乎非常繁琐。 - Baum mit Augen
2
嗨 Baum。我刚刚使用了自己的向量实现。.size()方法返回一个有符号整数,使一切变得顺畅。如果您使用std::vector,则必须使用std::size_t。 - InsideLoop
4
这取决于情况。没有简单的规则,而且std::vector<>::size返回无符号类型的事实令人遗憾,因为它会引起问题。但由于我很少使用std::vector<>::size,所以这不是问题。两个迭代器之间的差异是有符号类型,并且最终更经常使用。 - James Kanze

4

对我而言,无符号大小总是最有意义的,因为你不能在数组中有-32个元素,始终将大小/长度作为有符号量考虑是非常可怕的。

你提到的特殊情况可以通过编写代码来解决,在第一个情况下,如果v为空,可以在进入循环之前终止它(这看起来并不常见,遍历除最后一个元素外的所有元素?)。


4
遍历除最后一个元素外的所有元素在许多例子中都很有用。我想到了冒泡排序或者遍历矩阵的M(i,i+1)(主对角线上方的对角线)。我理解“它确实传达了整数是非负数”的意思,但大多数人在C语言中使用int来遍历数组,并感觉良好。 - InsideLoop
6
索引是一个数值,而无符号整数类型作为数值并不表现良好。(例如 abs(i1 - i2) 不会给出这两个索引之间的差异。)理想情况下,索引应该是一种范围类型,但 C++ 没有范围类型。使用 unsigned 来伪造范围类型也没有帮助,因为它仍然无法控制上限范围。在 C++ 中,无符号整数类型被设计用于一些特定的用途,比如位操作或者当真正需要模运算时。 - James Kanze
1
@JamesKanze:问题在于无符号类型有两种用例(自然数和模2^N同余的环),它们具有不同的要求,但是C语言没有使用单独的类型,而是将小于“int”的无符号类型作为第一种类型,而大于“int”的无符号类型则表现为另一种类型。添加不同的类型,例如“unumN_t”和“uwrapN_t”,前者保证升级为有符号类型,后者保证不会被升级,可以使事情变得更加清晰。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接