C++中的size_t和ptrdiff_t用于负索引数组

5
我在选择 size_tptrdiff_t 作为索引类型时遇到了困难,因为它需要能够存储负值。
确切地说,在我的代码中,我需要实现一个数组。我在构造函数中以 size_t 类型接收其长度,并且当我重载 [] 运算符 时,我希望索引的类型为 ptrdiff_t(而不是 size_t),因为我想允许负索引,就像这个例子所示:
std::size_t length = 50;
MyVector<int> vec(length);
vec[0] = 10;

MyVector<int> vec2 = vec+1;
std::cout << vec2[-1] << std::endl; //should print 10

所述设计引起的问题是可用索引范围受到ptrdiff_t的最大值限制,在一些机器上,这个上限小于size_t的最大值
std::numeric_limits<std::ptrdiff_t>::max() < std::numeric_limits<std::size_t>::max() 因此,问题在于用户可能创建一个大小大于ptrdiff_t的最大值(当然仍在size_t的范围内)的数组,但他将无法访问超过ptrdiff_t的最大值后继续的数组元素,因为它们的索引会溢出成一个负数。在我的机器上,这将使可用索引减少一半!(因为size_tptrdiff_t都是64位的,但一个是unsigned,另一个是signed
以下是我想到的解决方案,但遗憾的是没有一种是完美的:
  1. In the constructor, accept a length of type ptrdiff_t instead of size_t, and add a check that verifies that the given length is not negative.

    Pros: It solves the issue, since now I would be able ot access all the elements in the array, and still allow for negative indexes.

    Cons: It limits the maximum possible length of the array. (e.g. like I said earlier, in my machine it cuts by half)

  2. Leave things as they are, but in the [] operator, cast the index to type size_t, and make use of the fact that negative value would overflow.

    i.e. to the given index, add the difference between the element we're currently pointing to, and the

    for example, in my example before, since vec2 points to the second element in the arary, the [] operator would look something like

    template<class T>
    T& MyVector<T>::operator[] (std::ptrdiff_t index) {
        //Since vec2 points to the second element, we add 1.
        //For vec, we would add 0 since it points at the 
        //first element in the array.
        std::size_t actual_index = static_cast<std::size_t>(index + 1);
    
        //Do boundary checking
    
        return this->ptr[actual_index];
    }
    

    Pros: We're now able to access all the elements in the array.

    Cons: The usage becomes clumsy and ugly. For example, if we create a vector of size std::numeric_limits<std::size_t>::max(), then in order to access the last element, we need to access the '-1' element:

    MyVector<int> big_vector(std::numeric_limits<std::size_t>::max());
    big_vector[-1] = 5; // big_vector[-1] is the last element in the array.
    
    MyVector<int> big_vector2 = big_vector + 1;
    std::cout << big_vector2[-2] << std::endl; // would print 5, since big_vector2 points at the second element in the array
    

2
如果您已经使用64位大小,那么请使用解决方案#1。您甚至不能指望拥有9艾字节的操作内存,对吗? - The Paramagnetic Croissant
这只会影响字符类型,并且在典型的架构上,整数包装会“自动”解决问题——如果你在表达式中小心处理的话。无论如何,为了防止潜在的整数溢出,正确地证明C++代码是一个更大的问题,需要非常小心和测试,因此在投入过多精力之前,请考虑它是否真的是您的应用程序的要求。 - doynax
你可能有两个重载... - Jarod42
@TheParamagneticCroissant 确实如此,但我认为标准没有提到 std::ptrdiff_t 的下限。因此,理论上可能存在一种机器,其中 std::size_t 长度为 64 位,但 std::ptrdiff_t 只有 16 位,这对于数组大小来说是不可接受的太低了。 - Mikrosaft
1
@Mikrosaft:虽然 ptrdiff_t 可能比 size_t 小,但这种架构非常罕见。编写完全可移植的 C/C++ 代码以兼容所有奇怪的系统几乎是不可能的(例如需要大量的注意、笨拙的解决方案和由于非传统平台的稀缺性而几乎不可能进行的测试)。编写 C++ 代码的典型方式是选择你要支持的实现选择子集,然后在之后重新测试/审查,以确定你的猜测是否正确。大多数代码不需要在 24 位 DSP 上运行。 - doynax
1个回答

0
我在选择索引类型时遇到了困难,不知道是该选用 size_t 还是 ptrdiff_t,因为它需要能够存储负值。
你为什么要限制自己只使用 size_t 和 ptrdiff_t?其他整数类型呢?
你有实际计划去实现一个包含 4,294,967,296 个元素的容器吗?(即 2 ^ 32)
我认为今天没有任何平台会分配那么多内存,至少消费级电脑不会。你是在专门的硬件上工作吗?
对于通用目的的 C++,你可以安全地编写一个接受 std::size_t 的构造函数,用于一个以 std::ptrdiff_t 为索引的类。
你可能需要配置所接受的索引范围,这样如果你接受负索引,有效的索引范围应该是 (-max32bit, max32bit)。
这意味着,如果你尝试使用超出范围 (max32bit, max64bit) 的索引来使用该类,你的类应该抛出异常。
我的解决方案将在构造函数中接受一个std::size_t(并清楚地说明索引范围从-max(ptrdiff_t)到max(ptrdiff_t),而不是从0到max(std::size_t)),然后使用std::ptrdiff_t对类进行索引。

你确定初始长度可以为负数吗? - Serge Ballesta
初始长度不能为负数。这就是我说该类应该有一个接受 std::size_t 大小的构造函数的原因。 - utnapistim
16GB并不算很多。 - Don Reba
我只想说,文档应该说明初始长度应在范围0到max(ptrdiff_t)之间。 - Serge Ballesta

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