无限数组C++在一个表达式中使用两个新值调整数组大小

Infinite array C++ resizing the array with two new values in one expression

本文关键字:数组 新值 两个 调整 表达式 C++ 一个 无限      更新时间:2023-10-16

我已经开始使用C++中的模板实现无限数组。除了在一个表达式中添加两个新项的特殊情况外,添加整数效果很好,这需要一个接一个地调整两个大小(参见下文)。

#include <iostream>
#include <cstddef>
#include <new>
#include <string.h>
template <typename T>
struct infinite_array {
infinite_array();
auto operator[](unsigned long long idx) -> T&;
auto size() const -> unsigned long long;
void resize(unsigned long long idx);
private:
T *data;
unsigned long long array_length;
};
template <typename T>
void infinite_array<T>::resize(unsigned long long idx)
{
std::cout << "Resize with idx " << idx << std::endl;
T* temp = new T[idx];
memset(temp, 0, sizeof(T) * idx);
for (int i = 0; i < array_length; ++i) {
temp[i] = data[i];
std::cout <<  temp[i] << " ";
}
std::cout << std::endl; 
//std::copy(data, data+size(), temp);
delete [] data;
data = temp;
array_length = idx;
}
template <typename T>
infinite_array<T>::infinite_array() 
{
data = NULL;
array_length = 0;
}
template <typename T>
auto infinite_array<T>::size() const -> unsigned long long {
//array_length = sizeof(data)/sizeof(T);
return array_length;
}
template <typename T>
auto infinite_array<T>::operator[](unsigned long long idx) -> T& {
//std::cout << "Accessing element at idx " << idx << std::endl;
if (idx+1 > size()) {
resize(idx+1);  
}
return data[idx];
}
int main() {
infinite_array<int> ar; 
for (int i = 0; i < 10; ++i) {
ar[i] = i;
}
// PROBLEM: ONLY ar[31] is initialized successfully to 10
ar[30] = ar[31] = 10;
for (int i = 0; i < ar.size(); ++i)
std::cout << ar[i] << ' ';  
std::cout << std::endl;
return 0;
}

恐怕没有办法解决您的问题,因为您无法控制对operator[]的调用顺序。这就是问题所在:

  • 编译器选择首先评估ar[30],这将调整数组的大小并返回对其一个元素的引用。

  • 之后,对ar[31]进行求值,再次调整数组的大小,返回对新数组元素之一的另一个引用旧引用仍然指向旧数组中的元素(已删除!)

  • 最后,编译器执行赋值,为两个元素分配10。但是,由于其中一个元素位于已删除的旧数组中,因此在新数组中看不到它。

简单的事实是,不能像这样将调用链接到operator[],不能绕过编译器可以按任何顺序执行调用的事实。


此外:逐个调整缓冲区的大小通常是个坏主意,其复杂性是二次型的。典型的代码使用至少因子2的增量。精确的因子并不那么相关,相关的是你使用了一个因子,因为这样你就把复杂性降到了O(n)。值2只是空间和时间开销之间的一个很好的折衷。

通常,您不希望让您的访问器方法(at()operator[])调整数组的大小,因为这会违反关注点的分离(每个函数应该做1件事-调整大小需要做2件事)。

标准库实现std::vector的方式:如果您使用at()并提供一个越界地址,它将抛出一个异常(如果使用operator[],则为UB)。

遇到的问题

ar[30] = ar[31] = 10;

如果必须调整数组的大小,那么两个调用都必须调整它的大小。这与i = i++ + ++i;(也是UB)的情况非常相似。当您将大小调整为31时,您有一个临时缓冲区,并将新元素的值设置为10。当您将大小调整为32时,您有一个(不同的)临时缓冲区,并将新元素的值设置为10。当后一个返回时,它没有前一个的值,所以只写1。要解决此问题,请分开操作。