Here is a second follow up on Reusable storage for array of objects and Reusable storage for array of objects V2, taking into account the provided answers.
The following code should be compliant at least with gcc
, clang
, msvc
and for C++14 and beyond.
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iostream>
#include <limits>
#include <new>
#include <type_traits>
// Object constructible from a double
// forcing alignment
struct alignas(16) SFloat {
float val = 0.f;
SFloat() = default;
SFloat(const double v) : val(static_cast<float>(v)){};
SFloat& operator=(SFloat&& f) {
val = f.val;
return *this;
}
~SFloat() = default;
};
// Serialization of Float objects
std::ostream& operator<<(std::ostream& o, SFloat const& f) {
return o << f.val;
}
// 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
std::function<void(Buffer*)> Destructors = [](Buffer*) noexcept {};
// buffer address
unsigned char* Storage = nullptr;
// aligned buffer address
unsigned char* AlignedStorage = 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
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
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) {
Destructors = [](Buffer*) {};
throw std::bad_alloc();
}
Storage = NewStorage;
StorageSizeInBytes = RequiredSize;
}
AlignedStorage = Storage + OffsetForAlignment(Storage, alignof(T));
// initializing an dynamic array of T on the storage
T* tmp = ::new (Storage) T[Count];
// update nb of objects
CountOfStoredObjects = Count;
// create destructors functor
Destructors = [](Buffer* pobj) noexcept {
T* ToDestruct = reinterpret_cast<T*>(pobj->AlignedStorage);
// Delete elements in reverse order of creation
while (pobj->CountOfStoredObjects > 0) {
--(pobj->CountOfStoredObjects);
ToDestruct[(pobj->CountOfStoredObjects)].~T();
}
::operator delete[](ToDestruct, ToDestruct);
};
return tmp;
}
~Buffer() noexcept {
// Ending objects lifetime
Destructors(this);
// Releasing storage
std::free(Storage);
}
};
int main() {
constexpr std::size_t N0 = 7;
constexpr std::size_t N1 = 3;
Buffer B;
std::cout << "Test on SomeClass\n";
SFloat* PSFloat = B.DefaultAllocate<SFloat>(N0);
PSFloat[0] = 3.14;
*(PSFloat + 1) = 31.4;
PSFloat[2] = 314.;
std::cout << PSFloat[0] << '\n';
std::cout << PSFloat[1] << '\n';
std::cout << *(PSFloat + 2) << '\n';
std::cout << "Test on float\n";
// reallocating, possibly using existing storage, for a different type and
// size
float* PFloat = B.DefaultAllocate<float>(N1);
PFloat[0] = 3.14f;
*(PFloat + 1) = 31.4f;
PFloat[2] = 314.f;
std::cout << PFloat[0] << '\n';
std::cout << PFloat[1] << '\n';
std::cout << *(PFloat + 2) << '\n';
return 0;
}