В чем преимущество использования std::allocator вместо new в C++?
Я только что прочитал о std::allocator
. На мой взгляд, сложнее использовать его вместо использования new
и delete
.
С allocator
мы должны явно выделить память кучи, построить ее, уничтожить ее, а затем, наконец, освободить память. Так почему же он был создан?
в каких случаях его можно использовать и когда его следует использовать вместо new и delete?
7 ответов
std::allocator
является распределителем памяти по умолчанию для стандартных контейнеров библиотеки, и вы можете заменить свои собственные распределители. Это позволяет управлять распределением памяти стандартными контейнерами. Но я не думаю, что ваш вопрос о std::allocator
в частности, но скорее стратегия выделения памяти, а затем констукция объектов в этой памяти, а не использование new T[N]
, например.
и причина этого в том, что new T[N]
не позволяет вам контролировать то, что вызываются конструкторы. И это заставляет вас строить все объекты одновременно. Это ужасно для целей, например, std::vector
где вы только хотите выделить изредка.
С помощью распределителя необработанной памяти вы можете выделить определенный объем памяти, который определяет вашу емкость. Затем, когда пользователь добавляет элементы в вектор (используя конструктор по своему выбору), вы можете создавать объекты на месте в этой памяти.
затем, когда вы закончите память вы выделяете больше, обычно вдвое больше. Если std::vector
используется new T[N]
, он должен был бы перераспределять каждый раз, когда вы хотите добавить или удалить элемент, что было бы ужасно для производительности. Вы также будете вынуждены использовать конструктор по умолчанию для всех объектов, что накладывает ненужное ограничение на типы объектов std::vector
можно провести.
на мой взгляд, сложнее использовать его вместо использования new и delete.
да, но он не предназначен для замены new
и delete
, оно служит другой цели.
С помощью распределителя мы должны явно выделить память кучи, построить ее, уничтожить ее, а затем, наконец, освободить память.
так почему же он был создан?
потому что иногда вы хотите отделить выделение и строительство в два этапа (и аналогично разделению уничтожения и освобождения на два этапа). Если вы не хотите этого делать, не используйте распределитель, используйте new
вместо.
в каких случаях его можно использовать и когда его следует использовать вместо new и delete?
когда вам нужно поведение распределителя, а не поведение new
и delete
, очевидно! Типичным случаем является реализация контейнер.
рассмотрим следующий код:
std::vector<X> v;
v.reserve(4); // (1)
v.push_back( X{} ); // (2)
v.push_back( X{} ); // (3)
v.clear(); // (4)
здесь (1) необходимо выделить достаточно памяти для четырех объектов, но не их создания. Затем строки (2) и (3) должны создавать объекты в выделенной памяти. Затем строка (4) должна уничтожить эти объекты, но не освободить память. Наконец, в деструкторе вектора вся память может быть освобождена.
поэтому вектор не может просто использовать new X()
или delete &m_data[1]
для создания и уничтожения объектов, он должен выполнять распределение / освобождение отдельно от строительства/уничтожения. Аргумент шаблона распределителя контейнера определяет политику, которая должна использоваться для (de)выделения памяти и построения/уничтожения объектов, позволяя настраивать использование памяти контейнером. Политика по умолчанию -std::allocator
тип.
таким образом, вы используете распределитель, когда требуется распределитель (например, при использовании контейнера), и вы используете std::allocator
когда вы не хотите предоставлять пользовательский распределитель и просто хочу стандартный.
вы не используете распределитель в качестве замены new
и delete
.
распределители-очень важная концепция в STL. Каждый контейнер способен принимать распределитель в качестве аргумента. Затем распределения будут выполняться с использованием этого распределителя, а не стандартного.
это полезно, например, для выделения объектов одинакового размера в пуле, для повышения производительности или может потребоваться, если есть специальная область памяти, где должны жить ваши объекты.
шаги выделения и построения разделены, потому что, например, для вектор (std::vector::reserve
) важно иметь возможность выделять память для будущего использования, но не (пока) создавать объекты в ней.
Как пример вы можете написать распределитель как класс, содержащий массив фиксированного размера, и использовать этот массив для предоставления памяти для некоторого стандартного контейнера. Затем вы можете иметь экземпляр этого класса в стеке и, таким образом, полностью избежать выделения кучи для некоторой части вашей программы.
Посмотреть больше примеров вот в этом посте.
[...] когда он должен использоваться [...]
Если у вас есть конкретные потребности, и самое важное при написании собственных универсальных контейнеров.
ваш инстинкт прав. В 90% случаев, используйте new
. Однако обратите внимание на такие структуры, как, скажем,карта структуры данных. Одним из аргументов шаблона по умолчанию является class Alloc = allocator<pair<const Key,T>
, который определяет, как класс создает новые экземпляры вещей и управляет существующими экземплярами. Таким образом, теоретически можно создать собственный распределитель, а затем использовать его для существующих структур данных. С new
и delete
являются функциями, а не классами, необходимо иметь std::allocator
в представьте их и сделайте их допустимыми аргументами шаблона.
на std::allocator
был создан, чтобы позволить разработчикам больше контролировать, как выделяется память. Во многих встроенных системах память ограничена и имеет различные типы. Возможно, их не так уж много. Кроме того, распределение памяти хочет быть сведено к минимуму, чтобы избежать проблем фрагментации.
распределитель также допускает выделение из разных пулов памяти. Так, например, выделение блоков небольшого размера было бы более эффективным из небольшого пула памяти блоков.
new
и delete
- это прямой способ создать объект в динамической памяти и инициализировать его. Распределители намного больше, потому что они предлагают полный контроль над вышеупомянутыми фазами.
С распределителем мы должны явно выделить память из кучи, построить его, уничтожьте его, а затем, наконец, освободите память.
действительно распределители не должны использоваться для "нормального" кода, где new
и delete
так же будет штраф. Рассмотрим класс как std::map
, часто реализуется как дерево: вам нужно освободить весь лист всякий раз, когда объект, удерживаемый удаляется? Распределители позволяют вам уничтожить этот объект, но сохранить память, чтобы вам не пришлось требовать ее снова.
кроме того, вы можете специализировать распределитель для определенного типа, если вы знаете более оптимизированные методы для его управления, что невозможно для new
и delete
.
причина этого STL - член, чтобы дать разработчику больше контроля над памятью. Я имею в виду, например, что оператор new на самом деле не является одной операцией как таковой. В своем самом основном, он выполняет резервирование памяти, а затем заполняет это пространство объектом.
хотя я не могу на макушке придумать конкретный сценарий реального случая, вы должны использовать std::allocator
и такие, когда, возможно, уничтожение данного объекта может воздействие на другие объекты в памяти.
допустим, для аргумента вы создали какой-то вектор, каждый элемент которого дважды связан с каким-то другим объектом в памяти, и вы хотите, чтобы во время удаления указанного вектора объекты, связанные с удалением ссылки обратно на него.