Бесконечный массив C ++ изменяет размеры массива с двумя новыми значениями в одном выражении

0

Я начал применять бесконечный массив, используя шаблоны в 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;
}
  • 1
    Разве бесконечный массив не требует бесконечной памяти?
  • 1
    Вы просто рассказываете нам о том, что делаете, или у вас был актуальный вопрос?
Показать ещё 15 комментариев
Теги:
templates
infinite

2 ответа

2

Боюсь, не удастся исправить вашу проблему, потому что вы не можете контролировать порядок, в котором выполняются вызовы operator[]. И в этом проблема:

  • Ваш компилятор сначала попытается оценить ar[30], который изменит размер вашего массива и вернет ссылку на один из его элементов.

  • После этого ar[31] оценивается, массив снова изменяется, возвращается другая ссылка на один из новых элементов массивов. Старая ссылка все еще указывает на элемент в старом массиве (который удален!).

  • Наконец, вы компилятор выполняет назначение, назначая 10 для обоих элементов. Но так как один из этих элементов живет в старом удаленном массиве, вы не видите его в новом массиве.

Простая истина заключается в том, что вы не должны цеплять вызовы вашему operator[] как это, вы не можете обойти тот факт, что компилятору разрешено выполнять вызовы в любом порядке.


Кроме того, как правило, это плохая идея изменить размер любого буфера на один за другим, сложность этого квадратична. Типичный код использует приращения, по меньшей мере, фактора 2. Точный фактор не так важен, имеет значение то, что вы используете фактор, потому что тогда вы сокращаете сложность до O (n). Значение 2 - это просто хороший компромисс между пространственными и временными издержками.

  • 0
    Да, в Visual Studio они оцениваются с другой стороны, и это работает нормально
  • 0
    @NikosAthanasiou UB - это UB.
1

Как правило, вы не хотите, чтобы ваши методы доступа (at() или operator[]) изменяли размер массива, поскольку это нарушало бы разделение проблем (каждая функция должна делать 1 вещь - при изменении размера потребовалось бы сделать это 2).

Способ, которым стандартная библиотека реализует std::vector: если вы используете at() и предоставляете адрес за пределами, он генерирует исключение (если вы используете operator[], это UB).

Проблема, с которой вы сталкиваетесь

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

Если размер массива должен быть изменен, то оба вызова должны будут изменить его размер. Это очень похоже на то, что происходит с i = i++ + ++i; (что также является UB). Когда вы изменяете размер до 31, у вас есть временный буфер и задайте значение нового элемента равным 10. Когда вы измените размер до 32, у вас есть (другой) буфер temp и установите значение нового элемента равным 10. Когда позже один возвращается, он не имеет значения первого, поэтому записывается только 1. Чтобы исправить это, отделите свои операции.

  • 0
    Но для этого потребуется время от времени вызывать изменение размера вручную, что обременительно.
  • 0
    Я не понимаю, насколько сложно сделать так, чтобы 1) вы следовали SoC и 2) не вызывали UB. Отследить странную ошибку гораздо сложнее, потому что кто-то вызвал UB, чем убедиться, что вы получаете доступ к массиву в границах, IMO.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню