I am a bit confused about writing a custom allocator for aligned data. My aligned structs/classes typically derive AlignedData (passing the struct/class name as class template argument). The latter provides a custom operator new/new[]/delete/delete[]. I now want to write a custom allocator for these aligned structs, but I am confused about allocate/deallocate vs construct/destroy.
General
#include <malloc.h>
inline void *AllocAligned(size_t size, size_t alignment = 16) noexcept {
return _aligned_malloc(size, alignment);
}
template < typename DataT >
inline DataT *AllocAligned(size_t count) noexcept {
return static_cast< DataT * >(AllocAligned(count * sizeof(DataT)));
}
inline void FreeAligned(void *ptr) noexcept {
if (!ptr) {
return;
}
_aligned_free(ptr);
}
AlignedData
template< typename DataT >
struct AlignedData {
public:
static void *operator new(size_t size) {
const size_t alignment = __alignof(DataT);
// __declspec(align) on DataT is required
static_assert(alignment > 8,
"AlignedData is only useful for types with > 8 byte alignment.");
void * const ptr = AllocAligned(size, alignment);
if (!ptr) {
throw std::bad_alloc();
}
return ptr;
}
static void operator delete(void *ptr) noexcept {
FreeAligned(ptr);
}
static void *operator new[](size_t size) {
return operator new(size);
}
static void operator delete[](void *ptr) noexcept {
operator delete(ptr);
}
};
AlignedAllocator:
template< typename DataT, size_t AlignmentS = __alignof(DataT) >
struct AlignedAllocator {
public
using value_type = DataT;
using pointer = DataT *;
using reference = DataT &;
using const_pointer = const DataT *;
using const_reference = const DataT &;
using size_type = size_t;
using difference_type = ptrdiff_t;
template< typename DataU >
struct rebind {
public:
using other = AlignedAllocator< DataU, AlignmentS >;
rebind &operator=(const rebind &r) = delete;
rebind &operator=(rebind &&r) = delete;
private:
rebind() = delete;
rebind(const rebind &r) = delete;
rebind(rebind &&r) = delete;
~rebind() = delete;
};
AlignedAllocator() noexcept = default;
AlignedAllocator(
const AlignedAllocator< DataT, AlignmentS > &allocator) noexcept = default;
AlignedAllocator(
AlignedAllocator< DataT, AlignmentS > &&allocator) noexcept = default;
template< typename DataU >
AlignedAllocator(
const AlignedAllocator< DataU, AlignmentS > &allocator) noexcept {}
~AlignedAllocator() = default;
AlignedAllocator< DataT, AlignmentS > &operator=(
const AlignedAllocator< DataT, AlignmentS > &allocator) noexcept = delete;
AlignedAllocator< DataT, AlignmentS > &operator=(
AlignedAllocator< DataT, AlignmentS > &&allocator) noexcept = delete;
DataT *address(DataT &data) const noexcept {
return &data;
}
const DataT *address(const DataT &data) const noexcept {
return &data;
}
size_t max_size() const noexcept {
return (static_cast< size_t >(0) - static_cast< size_t >(1))
/ sizeof(DataT); // independent of definition of size_t
}
DataT *allocate(size_t count) const {
return new DataT[count];
}
template< typename DataU >
DataT *allocate(size_t count, const DataU *) const {
return allocate(count);
}
void deallocate(DataT *data, size_t count) const {
if (count == 0) {
return;
}
else if (count == 1) {
delete data;
}
else {
delete[] data;
}
}
template< typename DataU, typename... ConstructorArgsT >
void construct(DataU *data, ConstructorArgsT&&... args) const {
new ((void *)data) DataU(std::forward< ConstructorArgsT >(args)...); // this looks like an allocation after all?
}
template< typename DataU >
void destroy(DataU *data) const {
data->~DataU();
}
bool operator==(
const AlignedAllocator< DataT, AlignmentS > &rhs) const noexcept {
return true; // stateless allocator
}
bool operator!=(
const AlignedAllocator< DataT, AlignmentS > &rhs) const noexcept {
return false; // stateless allocator
}
};
I am not sure about the AlignmentS template parameter yet. I think of supporting non-AlignedData values as well. In this case, I would duplicate/replicate the content of my custom operator new/new[]/delete/delete[] in allocate and deallocate themselves.
↧