Эффективное перераспределение массивов в C++

как эффективно изменить размер массива, выделенного с помощью некоторого соответствующего стандартам распределителя C++? Я знаю это нет возможности для перераспределения в интерфейсе распределителя C++, но позволила ли нам версия C++11 работать с ними более легко? Предположим, у меня есть класс vec с копирования-оператор присваивания foo& operator=(const foo& x) определены. Если x.size() > this->size(), Я вынужден

  1. звонок распределителя.destroy() на всех элементах во внутреннем хранилище foo.
  2. звонок распределителя.освободить () от внутреннего хранилища foo.
  3. перераспределить новый буфер с достаточным пространством для x.size() элементы.
  4. используйте 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++ не будет реализован на большинстве текущих операционные системы, Если вам нужно было добавить стандартную функцию для этого.



даже если перераспределение существует, на самом деле, вы можете избежать только #2, который вы упомянули в своем вопросе в скопировать конструктор. Однако в случае роста внутреннего буфера перераспределение может сохранить эти четыре операции.

  1. внутренний буфер массива непрерывной? если это так, посмотрите ответ вашей ссылки
  2. , если не хэшированное дерево массива или список выбора может быть ваш выбор, чтобы избежать перераспределить.

интересно, что распределитель по умолчанию для g++ достаточно умен, чтобы использовать один и тот же адрес для последовательных освобождений и распределений больших размеров, если после окончания первоначально выделенного буфера достаточно неиспользуемого пространства. Хотя я не тестировал то, что собираюсь утверждать, я сомневаюсь, что существует большая разница во времени между malloc/realloc и allocate/deallocate/allocate.

Это приводит к потенциально очень опасному, нестандартному ярлыку, который может работать, если вы знаете, что после текущего буфера достаточно места, чтобы перераспределение не привело к новому адресу. (1) освободить текущий буфер без вызова alloc.destroy () (2) выделите новый, больший буфер и проверьте возвращенный адрес (3) Если новый адрес равен старому адресу, продолжайте счастливо; в противном случае вы потеряли свои данные (4) вызовите распределитель.construct () для элементов во вновь выделенном пространстве.

Я бы не стал использовать это ни для чего, кроме удовлетворяя свое любопытство, но он работает на G++ 4.6.