Я начал применять бесконечный массив, используя шаблоны в 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, у вас есть (другой) буфер temp и установите значение нового элемента равным 10. Когда позже один возвращается, он не имеет значения первого, поэтому записывается только 1. Чтобы исправить это, отделите свои операции.