Ответ 1
Я использую Howard Hinnant распределитель стека, и он работает как прелесть, но некоторые детали реализации немного мне непонятно.
Рад, что он работает для вас.
1. Почему используются глобальные операторы
new
иdelete
? Функции-членыallocate()
иdeallocate()
используют::operator new
и::operator delete
соответственно. Аналогично, функция-членconstruct()
использует новое глобальное размещение. Почему бы не разрешить определяемые пользователем глобальные или специфичные для класса перегрузки?
Нет особых причин. Не стесняйтесь изменять этот код любым способом, который лучше всего подходит для вас. Это должно было быть скорее примером, и это ни в коем случае не является совершенным. Единственными требованиями являются то, что распределитель и источник освобождения правильно выравнивают память и что элемент конструкции строит аргумент.
В С++ 11 элементы конструкции (и уничтожения) являются необязательными. Я бы рекомендовал вам удалить их из распределителя, если вы работаете в среде, которая предоставляет allocator_traits
. Чтобы узнать, просто удалите их и посмотрите, все ли еще компилируются.
2. Почему для юстировки установлено 16-битное кодирование, а не
std::alignment_of<T>
?
std::alignment_of<T>
, вероятно, будет работать нормально. В тот день я, вероятно, был параноиком.
3. Почему конструкторы и
max_size
имеют спецификацию исключенияthrow()
? Разве это не обескураживает (см., Например, более эффективные С++ Пункт 14.)? Действительно ли необходимо прекратить и прервать, когда исключение возникает в распределителе? Изменяется ли это с новым С++ 11noexcept
ключевое слово?
Эти члены просто никогда не бросят. Для С++ 11 я должен обновить их до noexcept
. В С++ 11 становится более важным украшать вещи с помощью noexcept
, особенно специальных членов. В С++ 11 можно определить, является ли выражение nothrow или нет. Код может разветвляться в зависимости от этого ответа. Код, который, как известно, является nothrow, скорее всего, приведет к тому, что общий код перейдет на более эффективный путь. std::move_if_noexcept
- канонический пример в С++ 11.
Не используйте throw(type1, type2)
когда-либо. Он устарел в С++ 11.
Использовать throw()
, когда вы действительно хотите сказать: это никогда не будет бросать, и если я ошибаюсь, завершите программу, чтобы я мог ее отладить. throw()
также устарел в С++ 11, но имеет замену: noexcept
.
4. Функция члена
construct()
была бы идеальным кандидатом для идеальной пересылки (вызывающему конструктору). Это способ написать С++ 11 совместимых распределителей?
Да. Однако allocator_traits
сделает это за вас. Пусть это. Std:: lib уже отлаживает этот код для вас. Контейнеры С++ 11 вызовут allocator_traits<YourAllocator>::construct(your_allocator, pointer, args...)
. Если ваш распределитель реализует эти функции, allocator_traits вызовет вашу реализацию, иначе он вызовет отладочную эффективную реализацию по умолчанию.
5. Какие еще изменения необходимы для обеспечения соответствия текущего кода С++ 11?
По правде говоря, этот распределитель на самом деле не является С++ 03 или С++ 11. Когда вы копируете распределитель, оригинал и копия должны быть равны друг другу. В этом дизайне это никогда не будет так. Однако эта вещь все еще просто работает во многих контекстах.
Если вы хотите сделать его строго соответствующим, вам нужен еще один уровень косвенности, так что копии будут указывать на один и тот же буфер.
Помимо этого, сборщики С++ 11 намного проще компоновать, чем распределители С++ 98/03. Здесь минимум, который вы должны сделать:
template <class T>
class MyAllocator
{
public:
typedef T value_type;
MyAllocator() noexcept; // only required if used
MyAllocator(const MyAllocator&) noexcept; // copies must be equal
MyAllocator(MyAllocator&&) noexcept; // not needed if copy ctor is good enough
template <class U>
MyAllocator(const MyAllocator<U>& u) noexcept; // requires: *this == MyAllocator(u)
value_type* allocate(std::size_t);
void deallocate(value_type*, std::size_t) noexcept;
};
template <class T, class U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;
template <class T, class U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) noexcept;
Возможно, вы можете рассмотреть возможность создания MyAllocator
Swappable и поместить в распределитель следующий вложенный тип:
typedef std::true_type propagate_on_container_swap;
Там есть несколько других регуляторов, которые вы можете настроить на распределителях С++ 11. Но все ручки имеют разумные значения по умолчанию.
Обновление
Выше я отмечаю, что мой распределитель стека не соответствует из-за того, что копии не равны. Я решил обновить этот распределитель соответствующим контроллеру С++ 11. Новый распределитель называется short_allocator и документирован здесь.
short_allocator отличается от распределителя стекав том, что "внутренний" буфер больше не является внутренним для распределителя, но теперь представляет собой отдельный объект "арены", который может быть расположен в локальном стеке или задан поток потоков или статических хранилищ. arena
не является потокобезопасным, хотя следите за этим. Вы можете сделать его потокобезопасным, если хотите, но это уменьшает отдачу (в конечном итоге вы будете изобретать malloc).
Это соответствует, потому что копии распределителей указывают на один и тот же внешний arena
. Обратите внимание, что единица N
теперь является байтами, а не числом T
.
Можно было преобразовать этот распределитель С++ 11 в распределитель С++ 98/03, добавив котельную плиты С++ 98/03 (typedefs, элемент конструкции, уничтожающий элемент и т.д.). Довольно утомительная, но простая задача.
Ответы на этот вопрос для нового short_allocator остаются без изменений.