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)); 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 * An _overlay view_ for the AllocationCluster to add functionality
* for adding / clearing extents and registering optional deleter functions. * for adding / clearing extents and registering optional deleter functions.
@ -91,9 +115,15 @@ namespace lib {
struct Destructor struct Destructor
{ {
Destructor* next; 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 struct Extent
: util::NonCopyable : util::NonCopyable
@ -112,7 +142,7 @@ namespace lib {
ManagementView view_; ManagementView view_;
StorageManager() = delete; ///< @warning used as _overlay view_ only, never created StorageManager() = delete; ///< @warning used as _overlay view_ only, never created
public: public:
static StorageManager& static StorageManager&
@ -135,6 +165,20 @@ namespace lib {
view_.extents.clear(); view_.extents.clear();
} }
void
addDestructor (void* dtor)
{
auto& destructor = * static_cast<Destructor*> (dtor);
getCurrentBlockStart()->dtors.push (destructor);
}
void
discardLastDestructor()
{
getCurrentBlockStart()->dtors.pop();
}
size_t size_t
determineExtentCnt() ///< @warning finalises current block determineExtentCnt() ///< @warning finalises current block
{ {
@ -152,12 +196,13 @@ namespace lib {
static auto determineStorageSize (AllocationCluster const&); static auto determineStorageSize (AllocationCluster const&);
private: private:
byte* Extent*
getCurrentBlockStart() const getCurrentBlockStart() const
{ {
return static_cast<byte*>(view_.storage.pos) void* pos = static_cast<byte*>(view_.storage.pos)
+ view_.storage.rest + view_.storage.rest
- EXTENT_SIZ; - EXTENT_SIZ;
return static_cast<Extent*> (pos);
} }
void void
@ -221,7 +266,22 @@ namespace lib {
ENSURE (allocRequest + OVERHEAD <= EXTENT_SIZ); ENSURE (allocRequest + OVERHEAD <= EXTENT_SIZ);
StorageManager::access(*this).addBlock(); StorageManager::access(*this).addBlock();
} }
void
AllocationCluster::registerDestructor (void* dtor)
{
StorageManager::access(*this).addDestructor (dtor);
}
void
AllocationCluster::discardLastDestructor()
{
StorageManager::access(*this).discardLastDestructor();
}
/* === diagnostics helpers === */ /* === diagnostics helpers === */

View file

@ -45,6 +45,8 @@
#include "lib/error.hpp" #include "lib/error.hpp"
#include "lib/nocopy.hpp" #include "lib/nocopy.hpp"
#include <type_traits>
#include <utility>
#include <memory> #include <memory>
@ -125,11 +127,10 @@ namespace lib {
template<class TY, typename...ARGS> template<class TY, typename...ARGS>
TY& TY& create (ARGS&& ...);
create (ARGS&& ...args)
{ template<class TY, typename...ARGS>
return * new(allot<TY>()) TY (std::forward<ARGS> (args)...); TY& createDisposable (ARGS&& ...);
}
template<typename X> template<typename X>
Allocator<X> Allocator<X>
@ -167,7 +168,14 @@ namespace lib {
return static_cast<X*> (allotMemory (cnt * sizeof(X), alignof(X))); return static_cast<X*> (allotMemory (cnt * sizeof(X), alignof(X)));
} }
typedef void (*DtorInvoker) (void*);
template<typename X>
void* allotWithDeleter();
void expandStorage (size_t); void expandStorage (size_t);
void registerDestructor (void*);
void discardLastDestructor();
bool _is_within_limits (size_t,size_t); bool _is_within_limits (size_t,size_t);
friend class test::AllocationCluster_test; friend class test::AllocationCluster_test;
@ -179,6 +187,75 @@ namespace lib {
//-----implementation-details------------------------ //-----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 } // namespace lib
#endif /*LIB_ALLOCATION_CLUSTER_H*/ #endif /*LIB_ALLOCATION_CLUSTER_H*/

View file

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

View file

@ -201,6 +201,7 @@ namespace test {
verifyInternals() verifyInternals()
{ {
CHECK (0==checksum); CHECK (0==checksum);
long markSum;
{ {
AllocationCluster clu; AllocationCluster clu;
// no allocation happened yet // no allocation happened yet
@ -265,34 +266,17 @@ namespace test {
CHECK (slot(0) == 0); CHECK (slot(0) == 0);
// deliberately fill up the first extent completely // 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) for (uint i=clu.storage_.rest; i>0; --i)
clu.create<uchar> (i); clu.create<uchar> (i);
SHOW_EXPR(size_t(currBlock())) CHECK (clu.storage_.rest == 0); // no space left in current extent
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 (posOffset() == BLOCKSIZ); CHECK (posOffset() == BLOCKSIZ);
SHOW_EXPR(clu.numBytes()) CHECK (clu.numBytes() == BLOCKSIZ - 2*sizeof(void*)); // now using all the rest behind the admin »slots«
CHECK (clu.numBytes() == BLOCKSIZ - 2*sizeof(void*));
CHECK (clu.numExtents() == 1); CHECK (clu.numExtents() == 1);
CHECK (slot(0) == 0); CHECK (slot(0) == 0);
CHECK (blk == currBlock()); // but still in the initial extent CHECK (blk == currBlock()); // but still in the initial extent
// trigger overflow and allocation of second extent // trigger overflow and allocation of second extent
char& c2 = clu.create<char> ('U'); 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 (blk != currBlock()); // allocation moved to a new extent
CHECK (getAddr(c2) == currBlock() + 2*sizeof(void*)); // c2 resides immediately after the two administrative »slots« CHECK (getAddr(c2) == currBlock() + 2*sizeof(void*)); // c2 resides immediately after the two administrative »slots«
CHECK (clu.storage_.rest == BLOCKSIZ - posOffset()); CHECK (clu.storage_.rest == BLOCKSIZ - posOffset());
@ -304,8 +288,33 @@ SHOW_EXPR(clu.numExtents())
CHECK (c1 == 'X'); CHECK (c1 == 'X');
CHECK (c2 == 'U'); CHECK (c2 == 'U');
CHECK (i3 == 42); 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;"/> <arrowlink COLOR="#faf8b2" DESTINATION="ID_1028046936" ENDARROW="Default" ENDINCLINATION="102;3;" ID="Arrow_ID_1495010693" STARTARROW="None" STARTINCLINATION="118;4;"/>
</node> </node>
</node> </node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716130547847" TEXT="automatischer Destruktor-Aufruf"> <node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716604728822" TEXT="automatischer Destruktor-Aufruf">
<icon BUILTIN="flag-yellow"/> <icon BUILTIN="pencil"/>
<node CREATED="1716130041704" ID="ID_1028046936" MODIFIED="1716130101251" TEXT="kann getriggert werden von StorageManager::Destructor"> <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;"/> <linktarget COLOR="#faf8b2" DESTINATION="ID_1028046936" ENDARROW="Default" ENDINCLINATION="102;3;" ID="Arrow_ID_1495010693" SOURCE="ID_350853857" STARTARROW="None" STARTINCLINATION="118;4;"/>
</node> </node>
@ -81815,12 +81815,25 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body> </body>
</html></richcontent> </html></richcontent>
</node> </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"/> <icon BUILTIN="flag-yellow"/>
</node> </node>
</node> </node>
</node> </node>
</node> </node>
</node>
<node COLOR="#338800" CREATED="1716130724127" ID="ID_727448469" MODIFIED="1716133312180" TEXT="low-level-Test"> <node COLOR="#338800" CREATED="1716130724127" ID="ID_727448469" MODIFIED="1716133312180" TEXT="low-level-Test">
<icon BUILTIN="button_ok"/> <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"> <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"> <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"/> <icon BUILTIN="flag-yellow"/>
</node> </node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130852555" ID="ID_1309770620" MODIFIED="1716130866602" TEXT="Test f&#xfc;r interne low-level-Mechanismen"> <node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1716130852555" ID="ID_1309770620" MODIFIED="1716605447662" TEXT="Test f&#xfc;r interne low-level-Mechanismen">
<icon BUILTIN="flag-yellow"/> <icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130874040" ID="ID_253325898" MODIFIED="1716133346231" TEXT="verifyInternals"> <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;"/> <arrowlink COLOR="#4173be" DESTINATION="ID_1783945506" ENDARROW="Default" ENDINCLINATION="204;17;" ID="Arrow_ID_1547348126" STARTARROW="None" STARTINCLINATION="292;-22;"/>
<icon BUILTIN="flag-yellow"/> <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"> <node COLOR="#338800" CREATED="1716552190584" ID="ID_1042988047" MODIFIED="1716593224739" TEXT="einen &#xdc;berlauf provozieren">
<icon BUILTIN="button_ok"/> <icon BUILTIN="button_ok"/>
</node> </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"> <node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716552266490" ID="ID_34007851" MODIFIED="1716552275535" TEXT="weiteren &#xdc;berlauf mit Alignment">
<icon BUILTIN="flag-yellow"/> <icon BUILTIN="flag-yellow"/>
</node> </node>