Library: implement optional invocation of destructors

This is the first draft, implementing the invocation explicitly
through a trampoline function. While it seems to work,
the formulation can probably be simplified....
This commit is contained in:
Fischlurch 2024-05-25 05:14:36 +02:00
parent 31f8664725
commit 71d5851701
5 changed files with 221 additions and 44 deletions

View file

@ -70,8 +70,32 @@ namespace lib {
{
Alloc::destroy (heap, static_cast<std::byte *> (storage));
}
/**
* Special allocator-policy for lib::LinkedElements
* - does not allow to allocate new elements
* - can hook up elements allocated elsewhere
* - ensure the destructor of all elements is invoked
*/
struct PolicyInvokeDtor
: lib::linked_elements::NoOwnership
{
/**
* while this policy doesn't take ownership,
* it ensures the destructor is invoked
*/
template<class X>
void
destroy (X* elm)
{
REQUIRE (elm);
elm->~X();
}
};
}
/**
* An _overlay view_ for the AllocationCluster to add functionality
* for adding / clearing extents and registering optional deleter functions.
@ -91,9 +115,15 @@ namespace lib {
struct Destructor
{
Destructor* next;
///////////////////////////////////////////////OOO store deleter function here
DtorInvoker dtor;
~Destructor()
{
if (dtor)
(*dtor) (this);
}
};
using Destructors = lib::LinkedElements<Destructor>;
using Destructors = lib::LinkedElements<Destructor, PolicyInvokeDtor>;
struct Extent
: util::NonCopyable
@ -112,7 +142,7 @@ namespace lib {
ManagementView view_;
StorageManager() = delete; ///< @warning used as _overlay view_ only, never created
StorageManager() = delete; ///< @warning used as _overlay view_ only, never created
public:
static StorageManager&
@ -135,6 +165,20 @@ namespace lib {
view_.extents.clear();
}
void
addDestructor (void* dtor)
{
auto& destructor = * static_cast<Destructor*> (dtor);
getCurrentBlockStart()->dtors.push (destructor);
}
void
discardLastDestructor()
{
getCurrentBlockStart()->dtors.pop();
}
size_t
determineExtentCnt() ///< @warning finalises current block
{
@ -152,12 +196,13 @@ namespace lib {
static auto determineStorageSize (AllocationCluster const&);
private:
byte*
Extent*
getCurrentBlockStart() const
{
return static_cast<byte*>(view_.storage.pos)
+ view_.storage.rest
- EXTENT_SIZ;
void* pos = static_cast<byte*>(view_.storage.pos)
+ view_.storage.rest
- EXTENT_SIZ;
return static_cast<Extent*> (pos);
}
void
@ -221,7 +266,22 @@ namespace lib {
ENSURE (allocRequest + OVERHEAD <= EXTENT_SIZ);
StorageManager::access(*this).addBlock();
}
void
AllocationCluster::registerDestructor (void* dtor)
{
StorageManager::access(*this).addDestructor (dtor);
}
void
AllocationCluster::discardLastDestructor()
{
StorageManager::access(*this).discardLastDestructor();
}
/* === diagnostics helpers === */

View file

@ -45,6 +45,8 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include <type_traits>
#include <utility>
#include <memory>
@ -125,11 +127,10 @@ namespace lib {
template<class TY, typename...ARGS>
TY&
create (ARGS&& ...args)
{
return * new(allot<TY>()) TY (std::forward<ARGS> (args)...);
}
TY& create (ARGS&& ...);
template<class TY, typename...ARGS>
TY& createDisposable (ARGS&& ...);
template<typename X>
Allocator<X>
@ -167,7 +168,14 @@ namespace lib {
return static_cast<X*> (allotMemory (cnt * sizeof(X), alignof(X)));
}
typedef void (*DtorInvoker) (void*);
template<typename X>
void* allotWithDeleter();
void expandStorage (size_t);
void registerDestructor (void*);
void discardLastDestructor();
bool _is_within_limits (size_t,size_t);
friend class test::AllocationCluster_test;
@ -179,6 +187,75 @@ namespace lib {
//-----implementation-details------------------------
template<class TY, typename...ARGS>
TY&
AllocationCluster::createDisposable (ARGS&& ...args)
{
return * new(allot<TY>()) TY (std::forward<ARGS> (args)...);
}
template<class TY, typename...ARGS>
TY&
AllocationCluster::create (ARGS&& ...args)
{
if constexpr (std::is_trivial_v<TY>)
return createDisposable<TY> (std::forward<ARGS> (args)...);
void* storage = allotWithDeleter<TY>();
try {
return * new(storage) TY (std::forward<ARGS> (args)...);
}
catch(...)
{
discardLastDestructor();
throw;
}
}
/**
* Establish a storage arrangement with a callback to invoke the destructor.
* @remark the purpose of AllocationCluster is to avoid deallocation of individual objects;
* thus the position and type of allocated payload objects is discarded. However,
* sometimes it is desirable to ensure invocation of object destructors; in this case,
* a linked list of destructor callbacks is hooked up in the storage extent. These
* callback records are always allocated directly before the actual payload object,
* and use a special per-type trampoline function to invoke the destructor, passing
* a properly adjusted self-pointer.
*/
template<typename X>
void*
AllocationCluster::allotWithDeleter()
{
/**
* Memory layout frame to place a payload object
* and store a destructor callback as intrusive linked list.
* @note this object is never constructed, but it is used to
* reinterpret the StorageManager::Destructor record,
* causing invocation of the destructor of the payload object,
* which is always placed immediately behind.
*/
struct TypedDtorInvoker
{
void* next;
DtorInvoker dtor;
X payload;
/** trampoline function: invoke the destructor of the payload type */
static void
invokePayloadDtor (void* self)
{
REQUIRE (self);
TypedDtorInvoker* instance = static_cast<TypedDtorInvoker*> (self);
instance->payload.~X();
}
};
TypedDtorInvoker* allocation = allot<TypedDtorInvoker>();
allocation->dtor = &TypedDtorInvoker::invokePayloadDtor;
registerDestructor (allocation);
return & allocation->payload;
}
} // namespace lib
#endif /*LIB_ALLOCATION_CLUSTER_H*/

View file

@ -323,12 +323,20 @@ namespace lib {
}
// template< class TY =N>
// TY& //_________________________________________
// emplace () ///< add object of type TY, using 0-arg ctor
// {
// return push (ALO::template create<TY>());
// }
/** extract the top-most element, if any
* @warning gives up ownership; if this list manages ownership,
* then the caller is responsible for deallocating the removed entry
* @return pointer to the removed element
*/
N*
pop()
{
N* elm = head_;
if (head_)
head_ = head_->next;
return elm;
}
/** prepend object of type TY, forwarding ctor args */
template<class TY =N, typename...ARGS>

View file

@ -201,6 +201,7 @@ namespace test {
verifyInternals()
{
CHECK (0==checksum);
long markSum;
{
AllocationCluster clu;
// no allocation happened yet
@ -265,34 +266,17 @@ namespace test {
CHECK (slot(0) == 0);
// deliberately fill up the first extent completely
SHOW_EXPR(size_t(currBlock()))
SHOW_EXPR(size_t(clu.storage_.pos))
SHOW_EXPR(clu.storage_.rest)
SHOW_EXPR(posOffset())
for (uint i=clu.storage_.rest; i>0; --i)
clu.create<uchar> (i);
SHOW_EXPR(size_t(currBlock()))
SHOW_EXPR(size_t(clu.storage_.pos))
SHOW_EXPR(clu.storage_.rest)
SHOW_EXPR(posOffset())
SHOW_EXPR(slot(0))
CHECK (clu.storage_.rest == 0);
CHECK (clu.storage_.rest == 0); // no space left in current extent
CHECK (posOffset() == BLOCKSIZ);
SHOW_EXPR(clu.numBytes())
CHECK (clu.numBytes() == BLOCKSIZ - 2*sizeof(void*));
CHECK (clu.numBytes() == BLOCKSIZ - 2*sizeof(void*)); // now using all the rest behind the admin »slots«
CHECK (clu.numExtents() == 1);
CHECK (slot(0) == 0);
CHECK (blk == currBlock()); // but still in the initial extent
// trigger overflow and allocation of second extent
char& c2 = clu.create<char> ('U');
SHOW_EXPR(size_t(currBlock()))
SHOW_EXPR(size_t(clu.storage_.pos))
SHOW_EXPR(clu.storage_.rest)
SHOW_EXPR(posOffset())
SHOW_EXPR(slot(0))
SHOW_EXPR(clu.numBytes())
SHOW_EXPR(clu.numExtents())
CHECK (blk != currBlock()); // allocation moved to a new extent
CHECK (getAddr(c2) == currBlock() + 2*sizeof(void*)); // c2 resides immediately after the two administrative »slots«
CHECK (clu.storage_.rest == BLOCKSIZ - posOffset());
@ -304,8 +288,33 @@ SHOW_EXPR(clu.numExtents())
CHECK (c1 == 'X');
CHECK (c2 == 'U');
CHECK (i3 == 42);
// allocate a "disposable" object (dtor will not be called)
SHOW_EXPR(clu.numBytes())
SHOW_EXPR(posOffset())
size_t pp = posOffset();
auto& o1 = clu.createDisposable<Dummy<2>> (4);
CHECK (o1.getID() == 4);
SHOW_EXPR(clu.numBytes())
SHOW_EXPR(posOffset())
SHOW_EXPR(checksum)
markSum = checksum;
CHECK (checksum == 4+4);
CHECK (alignof(Dummy<2>) == alignof(char));
CHECK (posOffset() - pp == sizeof(Dummy<2>));
// allocate a similar object,
// but this time also enrolling the destructor
pp = posOffset();
auto& o2 = clu.create<Dummy<2>> (8);
CHECK (o2.getID() == 8);
CHECK (checksum == markSum + 8+8);
SHOW_EXPR(clu.numBytes())
SHOW_EXPR(posOffset())
SHOW_EXPR(checksum)
}
CHECK (0==checksum);
SHOW_EXPR(checksum)
CHECK (checksum == markSum);
}

View file

@ -81768,8 +81768,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<arrowlink COLOR="#faf8b2" DESTINATION="ID_1028046936" ENDARROW="Default" ENDINCLINATION="102;3;" ID="Arrow_ID_1495010693" STARTARROW="None" STARTINCLINATION="118;4;"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716130547847" TEXT="automatischer Destruktor-Aufruf">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716604728822" TEXT="automatischer Destruktor-Aufruf">
<icon BUILTIN="pencil"/>
<node CREATED="1716130041704" ID="ID_1028046936" MODIFIED="1716130101251" TEXT="kann getriggert werden von StorageManager::Destructor">
<linktarget COLOR="#faf8b2" DESTINATION="ID_1028046936" ENDARROW="Default" ENDINCLINATION="102;3;" ID="Arrow_ID_1495010693" SOURCE="ID_350853857" STARTARROW="None" STARTINCLINATION="118;4;"/>
</node>
@ -81815,12 +81815,25 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130607500" ID="ID_641247354" MODIFIED="1716130643729" TEXT="brauche somit nur noch einen Trampolin-Pointer">
<node COLOR="#338800" CREATED="1716130607500" ID="ID_641247354" MODIFIED="1716604674352" TEXT="brauche somit nur noch einen Trampolin-Pointer">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1716604651319" ID="ID_398123666" MODIFIED="1716604673317" TEXT="mu&#xdf; sicherstellen, da&#xdf; Destroctor-Record und Objekt beieinander liegen">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1716606608657" ID="ID_1834443608" MODIFIED="1716606616344" TEXT="geht das nicht einfacher und sauberer?">
<icon BUILTIN="help"/>
<node CREATED="1716606623571" ID="ID_1928898649" MODIFIED="1716606636617" TEXT="im Grunde ist das hier ganz gew&#xf6;hnlicher Polymorphismus"/>
<node CREATED="1716606637660" ID="ID_1428982691" MODIFIED="1716606656903" TEXT="nur &#x201e;zu fu&#xdf;&#x201c; implementiert">
<icon BUILTIN="stop-sign"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716606714899" ID="ID_631957038" MODIFIED="1716606741395" TEXT="der Deleter-Invoker k&#xf6;nnte durch eine VTable ersetzt werden">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1716130724127" ID="ID_727448469" MODIFIED="1716133312180" TEXT="low-level-Test">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1716130730820" ID="ID_539013579" MODIFIED="1716130744964" TEXT="ich mache hier gef&#xe4;hrliche Sachen">
@ -81902,8 +81915,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1715783068923" ID="ID_34296369" MODIFIED="1715783077530" TEXT="Tests f&#xfc;r Error-Handling zur&#xfc;ckbauen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130852555" ID="ID_1309770620" MODIFIED="1716130866602" TEXT="Test f&#xfc;r interne low-level-Mechanismen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1716130852555" ID="ID_1309770620" MODIFIED="1716605447662" TEXT="Test f&#xfc;r interne low-level-Mechanismen">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130874040" ID="ID_253325898" MODIFIED="1716133346231" TEXT="verifyInternals">
<arrowlink COLOR="#4173be" DESTINATION="ID_1783945506" ENDARROW="Default" ENDINCLINATION="204;17;" ID="Arrow_ID_1547348126" STARTARROW="None" STARTINCLINATION="292;-22;"/>
<icon BUILTIN="flag-yellow"/>
@ -81926,6 +81939,16 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1716552190584" ID="ID_1042988047" MODIFIED="1716593224739" TEXT="einen &#xdc;berlauf provozieren">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1716605425287" ID="ID_1793345065" MODIFIED="1716605442774" TEXT="Objekt-Allokation mit registriertem Destruktor">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1716605457035" ID="ID_442879470" MODIFIED="1716605483931" TEXT="sieht gut aus...">
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1716605469169" ID="ID_680488739" MODIFIED="1716605487862" TEXT="zus&#xe4;tzliche Storage belegt"/>
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1716605475561" ID="ID_43688892" MODIFIED="1716605487863" TEXT="Destruktor wird aufgerufen"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716605489686" ID="ID_838033504" MODIFIED="1716605500558" TEXT="Verzeigerung explizit verifizieren">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716552266490" ID="ID_34007851" MODIFIED="1716552275535" TEXT="weiteren &#xdc;berlauf mit Alignment">
<icon BUILTIN="flag-yellow"/>
</node>