Я пытаюсь реализовать класс, который следует в памяти массивом произвольного типа:
template<class T>
class Buf
{
size_t n;
int refs;
explicit Buf(size_t n) : n(n) { }
// other declarations are here as appropriate
// Followed in memory by:
// T items[n];
};
Это было бы легко с operator new
:
template<class T>
Buf<T> *make_buf(size_t n)
{
// Assume the caller will take care of constructing the array elements
return new(operator new(sizeof(Buf<T>) + sizeof(T) * n)) Buf<T>(n);
}
template<class T>
void free_buf(Buf<T> *p)
{
// Assume the caller has taken care of destroying the array elements
p->~Buf<T>();
return operator delete(p);
}
template<class T>
T *get_buf_array(Buf<T> *p)
{
return reinterpret_cast<T *>(reinterpret_cast<char *>(p) + sizeof(Buf<T>));
}
Но теперь, как я могу реализовать это, используя некоторые стандартные распределитель SomeAllocator
?
Гарантируется ли это, что SomeAllocator::rebind<char>::other::allocate
вернет память, соответствующим образом выровненную для любого типа объекта? Если так, могу ли я в противном случае просто использовать распределитель некоторого типа символа? Если нет, есть ли у меня какие-либо альтернативы или эта задача вообще невозможна для распределителей? (В худшем случае я полагаю, что uintptr_t
и выровнять их вручную, но мне интересно, есть ли лучший способ.)
Я думал, что решение может быть путем создания условного раннего массива.
+-----------+
|Buf<T> |
+-------+---+---+-------+-------+
|T[0] | T[1] |T[2] | T[3]...
+-------+-------+-------+-------+
With the non-overlapping T[2], T[3], ... being the required array of T.
template<class T>
class Buf
{
size_t n;
int refs;
explicit Buf(size_t n) : n(n) { }
// other declarations are here as appropriate
// Followed in memory by:
// T items[n];
};
Количество разрушенных элементов будет:
const size_t lead = ( sizeof(Buf<T>) + sizeof(T) - 1) / sizeof(T);
Наконец, к сырой памяти я могу получить доступ
(reinterpret_cast<T*>( this ) )[ i + lead ];
Боюсь, вы делаете необоснованные предположения о том, что требует стандарт C ++. То, что вы пытаетесь сделать, может быть вообще невозможно.
Распределитель по умолчанию (новый или malloc) требуется для возврата указателя на блок памяти, который соответствующим образом выровнен для any complete object type with a fundamental alignment requirement
, Размер должен быть at least as large as the requested size
, Пользовательские распределители имеют разные требования в зависимости от того, что они выделяют. Распределитель для одного типа не гарантированно возвращает хранилище, соответствующим образом выровненное для другого. Конечно, если вы тот, кто реализует пользовательский распределитель, вы можете убедиться, что он возвращает то, что вам нужно.
Компилятор должен удовлетворять некоторым ограничениям в расположении памяти, но он не гарантирует, что что-то будет помещено в память сразу после чего-то другого. Могут быть вставлены байты заполнения для соответствия требованиям выравнивания.
Недавние стандарты C ++ предоставляют небольшую поддержку для обработки выравниваний. Возможно, где-то есть ответ для вас. Я подозреваю, что в основе этого лежат некоторые требования, о которых вы нам не сказали. Возможно, есть другой способ сделать это.
Я думаю, что стандартный совместимый распределитель вы STL совместимы? Поскольку вам не требуется, чтобы ваш распределитель использовался со структурой данных stl, на самом деле нет необходимости выполнять его требования, даже если вы можете сделать это, так как я думаю, что это отличный способ сделать это, и в этом случае вы можете реализовать ваш буфер, используя std :: vector с вашим собственным распределителем стиля stl в качестве параметра шаблона. Что касается гарантий выравнивания оператора new и оператора new [], я предлагаю вам взглянуть на:
Есть ли гарантия выравнивания возврата адреса при новой операции C ++?.
Если ваши проблемы с выравниванием относятся к примитивным типам, таким как double и т. Д., Вы в значительной степени охвачены std :: align, как вы можете видеть в http://en.cppreference.com/w/cpp/memory/align.
Однако, если у вас есть требования к выравниванию странного / большего размера, такие как выравнивание каждого элемента к строкам кэша и т. Д., Или если T — это тип с размером, где sizeof (T) mod выравнивание! = 0, у вас могут возникнуть проблемы при выделении массивов из T. В таких случаях, даже если первый элемент массива выровнен для соответствия требованию, это не означает, что все последующие элементы также будут выровнены.
Ограничьте выравнивание, связав распределитель со специализацией std::aligned_storage
,
typedef std::aligned_storage_t< 1, std::max( alignof (Buf<T>), alignof (T) ) >
unit_type; // lowest-common-denominator type for aligned allocation
std::size_t unit_count // number of unit_type array elements needed
= ( sizeof (Buf<T>) + sizeof (T) * n // = actual used storage
+ sizeof (unit_type) - 1 ) // + alignment padding
/ sizeof (unit_type); // divided by element size
typedef typename std::allocator_traits< SomeAllocator >::rebind_alloc< unit_type >
rebound;
rebound a( alloc_parm ); // Retain this somewhere if statefulness is allowed.
ptr = std::allocator_traits< rebound >::allocate( a, unit_count );
(Помните, что все доступ к распределителю проходит через allocator_traits
!)