Переадресация в специализированном распределителе STL с предварительно выделенным блоком
Я собираюсь создать собственный распределитель, предварительно распределяя большой блок (массив) для хранения элементов N
некоторого класса T
, а затем просто увеличивайте индекс внутри массива до запросов на распределение ресурсов.
Так как я не хочу инициализации для элементов в предварительно выделенном блоке, что-то вроде этого не будет работать:
T buffer[N];
потому что в этом случае конструктор T
будет вызываться для элементов N
блока.
Поскольку я понимаю, что std::aligned_storage
не вызывает конструктор T
, я думал об использовании std::aligned_storage
, что-то вроде этого:
std::aligned_storage<
N * sizeof(T),
std::alignment_of<T>::value
>::type buffer;
T* base = static_cast<T*>( static_cast<void*>(&buffer) );
И тогда распределитель может просто увеличивать базовый указатель, когда запрашивается выделение для Т (до (base+N)
), а Т можно построить на месте (с размещением new
), когда это необходимо.
Я хотел бы использовать эту схему для определения пользовательского распределителя для контейнеров STL. Однако мне кажется, что здесь может возникнуть проблема для переподключения. На самом деле, если мое понимание правильное, распределитель STL должен поддерживать восстановление из типа T
в тип U
, например. потому что контейнеры, такие как std::list<T>
(или другие контейнеры на основе node, такие как std::map
), используют распределители для выделения узлов, которые на самом деле не имеют тип T
, но другого типа U
(содержащий T
и другие "заголовка" для node).
Итак, применил бы вышеупомянутый подход std::aligned_storage
для перезаписи? Или (как я думаю) правильное выравнивание для T
не означает правильного выравнивания для другого другого типа U
?
Как решить эту проблему?
Как я могу определить вышеупомянутый buffer
, чтобы он работал и для перезаписи на другой тип U
?
Если эта проблема будет атакована с другой точки зрения? Если да, то что?
Ответы
Ответ 1
Вы на правильном пути.
Одна раздражающая информация заключается в том, что копии распределителей должны сравнивать одинаковые, даже конвертирующие (отскок) копии. Сравнивая равные средства, они могут освобождать друг друга указателями. Таким образом, контейнер, такой как std::list<int>
, перенастроит your_alloc<int>
на your_alloc<node<int>>
, а затем построит your_alloc<node<int>>
с помощью your_alloc<int>
. И технически ваш your_alloc<node<int>>
должен освободить указатель, выделенный your_alloc<int>
.
Здесь я пытаюсь выполнить это требование. Не стесняйтесь копировать/изменять/злоупотреблять этим кодом. Мое намерение состоит в том, чтобы воспитывать, а не стать мировым поставщиком распределителей (что в любом случае не будет прибыльным: -)).
В этом примере используется несколько иной подход к выравниванию: я знаю, что на моей интересующей платформе (OS X, iOS), что malloc
возвращает 16-байтную выровненную память и так, что все мои пользовательские распределители должны вернуться. Вы можете изменить это число на все, что подходит для вашей системы.
Эта настройка выравнивания означает, что один пул может безопасно поставлять несколько allocator<int>
и allocator<node<int>>
, так как все они выровнены по 16 байт (и это достаточно). И это также означает, что копии, даже конвертированные копии, могут обнаруживать, когда указатель указывает на буфер, поскольку все копии имеют один и тот же буфер.
Говоря немного иначе, комитет С++ эффективно указал, что распределители являются ссылочными типами: копии эквивалентны и указывают на один и тот же пул памяти.
Вы можете избавиться от обмана, используя пулы памяти, фактически встроенные в распределитель, но только с некоторыми контейнерами в некоторых реализациях, и вы не можете поддержать это с помощью цитаты из стандарта.