LUMIERA.clone/src/lib/allocator-handle.hpp
Ichthyostega c5679b0fd0 Library: Uninitialised-Storage array (see #1204)
Introduced as remedy for a long standing sloppiness:
Using a `char[]` together with `reinterpret_cast` in storage management helpers
bears danger of placing objects with wrong alignment; moreover, there are increasing
risks that modern code optimisers miss the ''backdoor access'' and might apply too
aggressive rewritings.

With C++17, there is a standard conformant way to express such a usage scheme.
 * `lib::UninitialisedStorage` can now be used in a situation (e.g. as in `ExtentFamily`)
   where a complete block of storage is allocated once and then subsequently used
   to plant objects one by one
 * moreover, I went over the code base and adapted the most relevant usages of
   ''placement-new into buffer'' to also include the `std::launder()` marker
2023-12-02 23:56:46 +01:00

143 lines
5.3 KiB
C++

/*
ALLOCATOR-HANDLE.hpp - front-end handle for custom allocation schemes
Copyright (C) Lumiera.org
2023, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file allocator-handle.hpp
** A front-end/concept to allow access to custom memory management.
** Minimalistic definition scheme for a functor-like object, which can be
** passed to client code, offering a callback to generate new objects into
** some custom allocation scheme not further disclosed.
**
** Lumiera employs various flavours of custom memory management, to handle
** allocation demands from performance critical parts of the application.
** Irrespective of the actual specifics of the allocation, typically there
** is some _instance_ of an allocator maintained within a carefully crafted
** context — leading to the necessity to dependency-inject a suitable front-end
** into various connected parts of the application, to allow for coherent use
** of allocation while avoiding tight coupling of implementation internals.
**
** Reduced to the bare minimum, the _ability to allocate_ can be represented
** as a functor, which accepts arbitrary (suitable) arguments and returns a
** reference to a newly allocated instance of some specific type; such an
** _allocation front-end_ may then be passed as additional (template)
** parameter to associated classes or functions, allowing to generate new
** objects at stable memory location, which can then be wired internally.
**
** @todo 6/2023 this specification describes a *Concept*, not an actual
** interface type. After the migration to C++20, it will thus be
** possible to mark some arbitrary custom allocator / front-end
** with such a concept, thereby documenting proper API usage.
**
** @see allocation-cluster.hpp
** @see steam::fixture::Segment
** @see steam::engine::JobTicket
*/
#ifndef LIB_ALLOCATOR_HANDLE_H
#define LIB_ALLOCATOR_HANDLE_H
#include "lib/error.hpp"
#include <cstddef>
#include <utility>
#include <list>
namespace lib {
/**
* Placeholder implementation for a custom allocator
* @todo shall be replaced by an AllocationCluster eventually
* @note conforming to a prospective C++20 Concept `Allo<TYPE>`
* @remark using `std::list` container, since re-entrant allocation calls are possible,
* meaning that further allocations will be requested recursively from a ctor.
* Moreover, for the same reason we separate the allocation from the ctor call,
* so we can capture the address of the new allocation prior to any possible
* re-entrant call, and handle clean-up of allocation without requiring any
* additional state flags.....
*/
template<typename TY>
class AllocatorHandle
{
struct Allocation
{
alignas(TY)
std::byte buf_[sizeof(TY)];
template<typename...ARGS>
TY&
create (ARGS&& ...args)
{
return *new(&buf_) TY {std::forward<ARGS> (args)...};
}
TY&
access()
{
return * std::launder (reinterpret_cast<TY*> (&buf_));
}
void
discard() /// @warning strong assumption made here: Payload was created
{
access().~TY();
}
};
std::list<Allocation> storage_;
public:
template<typename...ARGS>
TY&
operator() (ARGS&& ...args)
{ // EX_STRONG
auto pos = storage_.emplace (storage_.end()); ////////////////////////////////////////////////////TICKET #230 : real implementation should care for concurrency here
try {
return pos->create (std::forward<ARGS> (args)...);
}
catch(...)
{
storage_.erase (pos); // EX_FREE
const char* errID = lumiera_error();
ERROR (memory, "Allocation failed with unknown exception. "
"Lumiera errorID=%s", errID?errID:"??");
throw;
}
}
/** @note need to do explicit clean-up, since a ctor-call might have been failed,
* and we have no simple way to record this fact internally in Allocation,
* short of wasting additional memory for a flag to mark this situation */
~AllocatorHandle()
try {
for (auto& alloc : storage_)
alloc.discard();
}
ERROR_LOG_AND_IGNORE (memory, "clean-up of custom AllocatorHandle")
};
} // namespace lib
#endif /*LIB_ALLOCATOR_HANDLE_H*/