Подтвердить что ты не робот

Вопросы о распределителе стеков Hinnant

Я использую Howard Hinnant распределитель стека, и он работает как шарм, но некоторые детали реализации немного неясны для меня.

  • Почему используются глобальные операторы new и delete? Функции-члены allocate() и deallocate() используют ::operator new и ::operator delete соответственно. Аналогично, функция-член construct() использует глобальное размещение new. Почему бы не разрешить какие-либо пользовательские глобальные или специфичные для класса перегрузки?
  • Почему для выравнивания установлено 16-битное кодирование, а не std::alignment_of<T>?
  • Почему конструкторы и max_size имеют спецификацию исключения throw()? Разве это не обескураживает (см., Например, более эффективный С++, пункт 14.)? Действительно ли необходимо прекратить и прервать, когда в распределителе возникает исключение? Изменяется ли это с новым ключевым словом С++ 11 noexcept?
  • Функция члена construct() будет идеальным кандидатом для идеальной пересылки (вызывающему конструктору). Является ли это способом написания сопоставимых распределителей С++ 11?
  • Какие еще изменения необходимы для обеспечения соответствия текущего кода С++ 11?
4b9b3361

Ответ 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.)? Действительно ли необходимо прекратить и прервать, когда исключение возникает в распределителе? Изменяется ли это с новым С++ 11 noexcept ключевое слово?

Эти члены просто никогда не бросят. Для С++ 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 остаются без изменений.