Here is a thirdfollow up on Reusable storage for array of objects, Reusable storage for array of objects V2 and Reusable storage for array of objects V3, taking into account the provided answers.
The following code should be compliant at least with gcc
, clang
, msvc
and for C++14 and beyond.
In this version, I leave only the core code for lisibility and provide a testbench on Compiler Explorer.
#include <cstddef>
#include <cstdint>
// @ adding forgotten cstdlib
#include <cstdlib>
#include <limits>
#include <new>
#include <type_traits>
// type-punning reusable buffer
// holds a non-typed buffer (actually a char*) that can be used to store any
// types, according to user needs
class Buffer {
private:
// destructing functor storage
// required to call the correct object destructors
void (*Destructors)(Buffer*) noexcept = nullptr;
// storage address
unsigned char* Storage = nullptr;
// objects address
unsigned char* Objects = nullptr;
// number of stored elements
std::size_t CountOfStoredObjects = 0;
// buffer size in bytes
std::size_t StorageSizeInBytes = 0;
// computes the smallest positive offset in bytes that must be applied to
// Storage in order to have alignment a Alignment is supposed to be a valid
// alignment
static std::size_t OffsetForAlignment(unsigned char const* const Ptr,
std::size_t Alignment) noexcept {
std::size_t Res = static_cast<std::size_t>(
reinterpret_cast<std::uintptr_t>(Ptr) % Alignment);
if (Res) {
return Alignment - Res;
} else {
return 0;
}
}
public:
// allocates a char buffer large enough for Counts object of type T and
// default-construct them
template <typename T>
T* DefaultAllocate(const std::size_t Count) {
if (Count > (std::numeric_limits<std::size_t>::max() - alignof(T)) /
sizeof(T)) {
throw std::bad_alloc();
}
// Destroy previously stored objects
if (Destructors) {
Destructors(this);
}
std::size_t RequiredSize = sizeof(T) * Count + alignof(T);
if (StorageSizeInBytes < RequiredSize) {
// Creating a storage of RequiredSize bytes
unsigned char* NewStorage = reinterpret_cast<unsigned char*>(
std::realloc(Storage, RequiredSize));
if (NewStorage == nullptr) {
throw std::bad_alloc();
}
Storage = NewStorage;
StorageSizeInBytes = RequiredSize;
}
unsigned char* AlignedStorage =
Storage + OffsetForAlignment(Storage, alignof(T));
// initializing an dynamic array of T on the storage
T* Tmp = ::new (AlignedStorage) T[Count];
Objects = reinterpret_cast<unsigned char*>(Tmp);
// update nb of objects
CountOfStoredObjects = Count;
// create destructors functor
Destructors = [](Buffer* PBuffer) noexcept {
PBuffer->Destructors = nullptr;
T* ToDestruct = reinterpret_cast<T*>(PBuffer->Objects);
// Delete elements in reverse order of creation
while (PBuffer->CountOfStoredObjects > 0) {
--(PBuffer->CountOfStoredObjects);
ToDestruct[(PBuffer->CountOfStoredObjects)].~T();
}
::operator delete[](ToDestruct, ToDestruct);
PBuffer->CountOfStoredObjects = 0;
PBuffer->Objects = nullptr;
};
return Tmp;
}
~Buffer() noexcept {
// Ending objects lifetime
if (Destructors) {
Destructors(this);
}
// Releasing storage
std::free(Storage);
}
};
[EDIT] pinpointing fixes with // @ ...
and replacing ```std::function``` by a simplier function pointer
std::function
. I had an issue with msvc/C++14 that was merely due to a missing compiler option. \$\endgroup\$