Ответ 1
Этот код был написан до того, как у меня был std::max_align_t
в моем ящике инструментов (который теперь живет в <cstddef>
). Я бы написал это как:
static const std::size_t alignment = alignof(std::max_align_t);
который в моей системе точно эквивалентен текущему коду, но теперь более портативен. Это выравнивание, которое new
и malloc
гарантированно возвращаются. И как только у вас есть этот "максимально выравниваемый" буфер, вы можете поместить в него массив любого типа. Но вы не можете использовать те же arena
для разных типов (по крайней мере, не разные типы, которые имеют разные требования к выравниванию). И по этой причине, возможно, было бы лучше шаблон arena
на второй size_t
, который равен alignof(T)
. Таким образом, вы можете предотвратить случайное использование тех же arena
типов с разными требованиями к выравниванию:
arena<N, alignof(T)>& a_;
Предполагая, что каждое выделение из arena
имеет одинаковые требования к выравниванию и предполагается, что буфер максимально выровнен, тогда каждое выделение из буфера будет соответствующим образом выровнено для T
.
например. на моей системе alignof(std::max_align_t) == 16
. Буфер с этим выравниванием может содержать массивы:
- Типы с
alignof == 1
. - Типы с
alignof == 2
. - Типы с
alignof == 4
. - Типы с
alignof == 8
. - Типы с
alignof == 16
.
Поскольку некоторые среды могут поддерживать типы, которые имеют требования "супер-выравнивания", добавленная мера безопасности должна заключаться в добавлении (скажем, в short_alloc
):
static_assert(alignof(T) <= alignof(std::max_align_t), "");
Если вы супер параноик, вы также можете проверить, что alignof(T)
является степенью 2, хотя сам стандарт С++ гарантирует, что это всегда будет true ([basic.align]/p4).
Обновление
Я более подробно рассмотрел эту проблему и считаю, что наилучшим решением является округление запрошенного размера размещения до следующего alignment
(как предложено OP). Я обновил "short_alloc" , чтобы сделать это на моем веб-сайте.
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
В особых ситуациях, когда вы знаете, что вам не нужны максимально выровненные распределения (например, vector<unsigned char>
), можно просто настроить alignment
соответствующим образом. И можно также short_alloc::allocate
пройти alignof(T)
до arena::allocate
и assert(requested_align <= alignment)
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n, std::size_t requested_align)
{
assert(requested_align <= alignment);
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
Это даст вам уверенность, что если вы скорректировали alignment
вниз, вы не отрегулировали его слишком далеко вниз.
Обновить снова!
Я обновил описание и код этого распределителя совсем немного из-за этого прекрасного вопроса (Я не обращал внимания на этот код в течение многих лет).
Проверка выравнивания, упомянутая в предыдущем обновлении, теперь выполняется во время компиляции (ошибки времени компиляции всегда превосходят ошибки во время выполнения, даже утверждают).
Оба параметра arena
и short_alloc
теперь настроены на выравнивание, чтобы вы могли легко настроить требования к выравниванию, которые вы ожидаете (и если вы догадываетесь, что это слишком мало, он попадает во время компиляции). По умолчанию этот параметр шаблона равен alignof(std::max_align_t)
.
Теперь функция arena::allocate
выглядит следующим образом:
template <std::size_t N, std::size_t alignment>
template <std::size_t ReqAlign>
char*
arena<N, alignment>::allocate(std::size_t n)
{
static_assert(ReqAlign <= alignment, "alignment is too small for this arena");
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
auto const aligned_n = align_up(n);
if (buf_ + N - ptr_ >= aligned_n)
{
char* r = ptr_;
ptr_ += aligned_n;
return r;
}
return static_cast<char*>(::operator new(n));
}
Благодаря шаблонам псевдонимов этот распределитель проще в использовании, чем когда-либо. Например:
// Create a vector<T> template with a small buffer of 200 bytes.
// Note for vector it is possible to reduce the alignment requirements
// down to alignof(T) because vector doesn't allocate anything but T's.
// And if we're wrong about that guess, it is a comple-time error, not
// a run time error.
template <class T, std::size_t BufSize = 200>
using SmallVector = std::vector<T, short_alloc<T, BufSize, alignof(T)>>;
// Create the stack-based arena from which to allocate
SmallVector<int>::allocator_type::arena_type a;
// Create the vector which uses that arena.
SmallVector<int> v{a};
Это не обязательно последнее слово в таких распределителях. Но, надеюсь, это прочная основа, на которой вы можете создавать свои собственные распределители.