Эффективное перераспределение массивов в C++
как эффективно изменить размер массива, выделенного с помощью некоторого соответствующего стандартам распределителя C++? Я знаю это нет возможности для перераспределения в интерфейсе распределителя C++, но позволила ли нам версия C++11 работать с ними более легко? Предположим, у меня есть класс vec
с копирования-оператор присваивания foo& operator=(const foo& x)
определены. Если x.size() > this->size()
, Я вынужден
- звонок распределителя.destroy() на всех элементах во внутреннем хранилище
foo
. - звонок распределителя.освободить () от внутреннего хранилища
foo.
- перераспределить новый буфер с достаточным пространством для
x.size()
элементы. - используйте std:: uninitialized_copy для заполнения хранилища.
есть ли способ, которым я более легко перераспределяю внутреннее хранилище foo
без необходимости проходить через все это? Я мог бы предоставить фактический образец кода, если вы считаете, что это будет полезно, но я чувствую, что это будет быть ненужным здесь.
6 ответов
на основе предыдущий вопрос, подход, который я использовал для обработки больших массивов, которые могли расти и сжиматься с разумной эффективностью, состоял в том, чтобы написать контейнер, подобный deque, который разбил массив на несколько страниц меньших массивов. Например, скажем, у нас есть массив из n элементов, мы выбираем размер страницы p и создаем 1 + n/p массивов (страниц) из p элементов. Когда мы хотим перераспределить и увеличить, мы просто оставляем существующие страницы там, где они есть, и распределяем новая страница. Когда мы хотим сжаться, мы освобождаем абсолютно пустые страницы.
недостатком является то, что доступ к массиву немного медленнее, в этом заданном и индексе i вам нужна страница = i / p и смещение на страницу i % p, чтобы получить элемент. Я считаю, что это все еще очень быстро, однако, и обеспечивает хорошее решение. Теоретически std:: deque должен делать что-то очень похожее, но для случаев, которые я пробовал с большими массивами, это было очень медленно. См. комментарии и примечания по связанному вопросу для новые подробности.
существует также неэффективность памяти в том, что, учитывая n элементов, мы всегда держим p-n % p элементов в резерве. т. е. мы только когда-либо выделяем или освобождаем полные страницы. Это было лучшее решение, которое я мог придумать в контексте больших массивов с требованием изменения размера и быстрого доступа, в то время как я не сомневаюсь, что есть лучшие решения, которые я хотел бы видеть.
аналогичная проблема также возникает, если
x.size() > this->size()
наfoo& operator=(foo&& x)
.
нет, это не так. Вы просто swap
.
нет функции, которая будет менять или возвращать 0
при сбое (для изменения размера). Я не знаю ни одной операционной системы, которая поддерживает такую функциональность, кроме того, что говорит вам, насколько велико конкретное распределение.
однако все операционные системы поддерживают реализацию realloc
, однако, это делает копию, если она не может изменить размер на месте.
таким образом, вы не можете иметь его, потому что язык C++ не будет реализован на большинстве текущих операционные системы, Если вам нужно было добавить стандартную функцию для этого.
есть C++11 rvalue ссылка и перемещение конструкторов.
есть большая прямая речь на них.
даже если перераспределение существует, на самом деле, вы можете избежать только #2, который вы упомянули в своем вопросе в скопировать конструктор. Однако в случае роста внутреннего буфера перераспределение может сохранить эти четыре операции.
- внутренний буфер массива непрерывной? если это так, посмотрите ответ вашей ссылки
- , если не хэшированное дерево массива или список выбора может быть ваш выбор, чтобы избежать перераспределить.
интересно, что распределитель по умолчанию для g++ достаточно умен, чтобы использовать один и тот же адрес для последовательных освобождений и распределений больших размеров, если после окончания первоначально выделенного буфера достаточно неиспользуемого пространства. Хотя я не тестировал то, что собираюсь утверждать, я сомневаюсь, что существует большая разница во времени между malloc/realloc и allocate/deallocate/allocate.
Это приводит к потенциально очень опасному, нестандартному ярлыку, который может работать, если вы знаете, что после текущего буфера достаточно места, чтобы перераспределение не привело к новому адресу. (1) освободить текущий буфер без вызова alloc.destroy () (2) выделите новый, больший буфер и проверьте возвращенный адрес (3) Если новый адрес равен старому адресу, продолжайте счастливо; в противном случае вы потеряли свои данные (4) вызовите распределитель.construct () для элементов во вновь выделенном пространстве.
Я бы не стал использовать это ни для чего, кроме удовлетворяя свое любопытство, но он работает на G++ 4.6.