Library: better let C++ handle the destructors

...what I've implemented yesterday is effectively the same functionality
as provided automatically by the C++ object system when using a virtual destructor.
Thus a much cleaner solution is to turn `Destructor` into a interface
and let C++ do all the hard work.

Verified in test: works as intended
This commit is contained in:
Fischlurch 2024-05-25 19:27:17 +02:00
parent 71d5851701
commit be398e950a
5 changed files with 124 additions and 165 deletions

View file

@ -100,7 +100,7 @@ namespace lib {
* An _overlay view_ for the AllocationCluster to add functionality
* for adding / clearing extents and registering optional deleter functions.
* @warning this is a tricky construct to operate each Allocation Cluster
* with the absolute necessary minimum of organisational overhead.
* with the absolute minimum of organisational overhead necessary.
* The key point to note is that StorageManager is layout compatible
* with AllocationCluster itself achieved through use of the union
* ManagementView, which holds a Storage descriptor member, but
@ -112,19 +112,10 @@ namespace lib {
*/
class AllocationCluster::StorageManager
{
struct Destructor
{
Destructor* next;
DtorInvoker dtor;
~Destructor()
{
if (dtor)
(*dtor) (this);
}
};
using Destructors = lib::LinkedElements<Destructor, PolicyInvokeDtor>;
/** Block of allocated storage */
struct Extent
: util::NonCopyable
{
@ -142,7 +133,7 @@ namespace lib {
ManagementView view_;
StorageManager() = delete; ///< @warning used as _overlay view_ only, never created
StorageManager() = delete; ///< @note used as _overlay view_ only, never created
public:
static StorageManager&
@ -166,16 +157,9 @@ namespace lib {
}
void
addDestructor (void* dtor)
attach (Destructor& dtor)
{
auto& destructor = * static_cast<Destructor*> (dtor);
getCurrentBlockStart()->dtors.push (destructor);
}
void
discardLastDestructor()
{
getCurrentBlockStart()->dtors.pop();
getCurrentBlockStart()->dtors.push (dtor);
}
@ -195,6 +179,7 @@ namespace lib {
static auto determineStorageSize (AllocationCluster const&);
private:
Extent*
getCurrentBlockStart() const
@ -240,7 +225,7 @@ namespace lib {
/**
* On shutdown of the AllocationCluster walks all extents and invokes all
* The shutdown of an AllocationCluster walks all extents and invokes all
* registered deleter functions and then discards the complete storage.
* @note it is possible to allocate objects as _disposable_ meaning
* that no destructors will be enrolled and called for such objects.
@ -254,6 +239,11 @@ namespace lib {
ERROR_LOG_AND_IGNORE (progress, "discarding AllocationCluster")
/** virtual dtor to cause invocation of the payload's dtor on clean-up */
AllocationCluster::Destructor::~Destructor() { };
/**
* Expand the alloted storage pool by a block,
* suitable to accommodate at least the indicated request.
@ -269,16 +259,9 @@ namespace lib {
void
AllocationCluster::registerDestructor (void* dtor)
AllocationCluster::registerDestructor (Destructor& dtor)
{
StorageManager::access(*this).addDestructor (dtor);
}
void
AllocationCluster::discardLastDestructor()
{
StorageManager::access(*this).discardLastDestructor();
StorageManager::access(*this).attach (dtor);
}

View file

@ -24,8 +24,8 @@
** Memory management for the low-level model (render nodes network).
** The model is organised into temporal segments, which are considered
** to be structurally constant and uniform. The objects within each
** segment are strongly interconnected, and thus each segment is
** being built in a single build process and is replaced or released
** segment are strongly interconnected, and thus each segment is
** created within a single build process and is replaced or released
** as a whole. AllocationCluster implements memory management to
** support this usage pattern.
**
@ -35,7 +35,7 @@
**
** @see allocation-cluster-test.cpp
** @see builder::ToolFactory
** @see frameid.hpp
** @see linked-elements.hpp
*/
@ -55,32 +55,17 @@ namespace lib {
namespace test { class AllocationCluster_test; } // declared friend for low-level-checks
/**
/**
* A pile of objects sharing common allocation and lifecycle.
* AllocationCluster owns a number of object families of various types.
* Each of those contains a initially undetermined (but rather large)
* number of individual objects, which can be expected to be allocated
* within a short timespan and which are to be released cleanly on
* destruction of the AllocationCluster. We provide a service creating
* individual objects with arbitrary ctor parameters.
* @warning make sure the objects dtors aren't called and object references
* aren't used after shutting down a given AllocationCluster.
* @todo implement a facility to control the oder in which
* the object families are to be discarded. Currently
* they are just purged in reverse order defined by
* the first request for allocating a certain type.
* @todo should we use an per-instance lock? We can't avoid
* the class-wide lock, unless also the type-ID registration
* is done on a per-instance base. AllocationCluster is intended
* to be used within the builder, which executes in a dedicated
* thread. Thus I doubt lock contention could be a problem and
* we can avoid using a mutex per instance. Re-evaluate this!
* @todo currently all AllocationCluster instances share the same type-IDs.
* When used within different usage contexts this leads to some slots
* remaining empty, because not every situation uses any type encountered.
* wouldn't it be desirable to have multiple distinct contexts, each with
* its own set of Type-IDs and maybe also separate locking?
* Is this issue worth the hassle? //////////////////////////////TICKET #169
* AllocationCluster owns a heterogeneous collection of objects of various types.
* Typically, allocation happens during a short time span when building a new segment,
* and objects are used together until the segment is discarded. The primary leverage
* is to bulk-allocate memory, and to avoid invoking destructors (and thus to access
* a lot of _cache-cold memory pages_ on clean-up). A Stdlib compliant #Allocator
* is provided for use with STL containers. The actual allocation uses heap memory
* in _extents_ of hard-wired size, by the accompanying StorageManager.
* @warning use #createDisposable whenever possible, but be sure to understand
* the ramifications of _not invoking_ an object's destructor.
*/
class AllocationCluster
: util::MoveOnly
@ -121,17 +106,11 @@ namespace lib {
};
Storage storage_;
public:
AllocationCluster ();
~AllocationCluster () noexcept;
template<class TY, typename...ARGS>
TY& create (ARGS&& ...);
template<class TY, typename...ARGS>
TY& createDisposable (ARGS&& ...);
template<typename X>
Allocator<X>
getAllocator()
@ -140,6 +119,13 @@ namespace lib {
}
template<class TY, typename...ARGS>
TY& create (ARGS&& ...);
template<class TY, typename...ARGS>
TY& createDisposable (ARGS&& ...);
/* === diagnostics === */
size_t numExtents() const;
@ -168,14 +154,32 @@ namespace lib {
return static_cast<X*> (allotMemory (cnt * sizeof(X), alignof(X)));
}
typedef void (*DtorInvoker) (void*);
class Destructor
: util::NonCopyable
{
public:
virtual ~Destructor(); ///< this is an interface
Destructor* next{nullptr};// intrusive linked list...
};
/** @internal storage frame with the actual payload object,
* which can be attached to a list of destructors to invoke
*/
template<typename X>
void* allotWithDeleter();
struct AllocationWithDestructor
: Destructor
{
X payload;
template<typename...ARGS>
AllocationWithDestructor (ARGS&& ...args)
: payload(std::forward<ARGS> (args)...)
{ }
};
void expandStorage (size_t);
void registerDestructor (void*);
void discardLastDestructor();
void registerDestructor (Destructor&);
bool _is_within_limits (size_t,size_t);
friend class test::AllocationCluster_test;
@ -187,13 +191,22 @@ namespace lib {
//-----implementation-details------------------------
/**
* Factory function: place a new instance into this AllocationCluster,
* but *without invoking its destructor* on clean-up (for performance reasons).
*/
template<class TY, typename...ARGS>
TY&
AllocationCluster::createDisposable (ARGS&& ...args)
{
return * new(allot<TY>()) TY (std::forward<ARGS> (args)...);
}
/**
* Factory function: place a new instance into this AllocationCluster;
* the object will be properly destroyed when the cluster goes out of scope.
* @note whenever possible, the #createDisposable variant should be preferred
*/
template<class TY, typename...ARGS>
TY&
AllocationCluster::create (ARGS&& ...args)
@ -201,59 +214,10 @@ namespace lib {
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;
using Frame = AllocationWithDestructor<TY>;
auto& frame = createDisposable<Frame> (std::forward<ARGS> (args)...);
registerDestructor (frame);
return frame.payload;
}

View file

@ -323,20 +323,6 @@ namespace lib {
}
/** 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

@ -290,14 +290,9 @@ namespace test {
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));
@ -309,11 +304,27 @@ SHOW_EXPR(checksum)
auto& o2 = clu.create<Dummy<2>> (8);
CHECK (o2.getID() == 8);
CHECK (checksum == markSum + 8+8);
SHOW_EXPR(clu.numBytes())
CHECK (posOffset() - pp > sizeof(Dummy<2>) + 2*sizeof(void*));
CHECK (slot(1) > 0);
CHECK (size_t(&o2) - slot(1) == 2*sizeof(void*)); // Object resides in a Destructor frame,
using Dtor = AllocationCluster::Destructor; // ... which has been hooked up into admin-slot-1 of the current extent
auto dtor = (Dtor*)slot(1);
CHECK (dtor->next == nullptr);
// any other object with non-trivial destructor....
string rands = lib::test::randStr(9);
pp = posOffset();
string& s1 = clu.create<string> (rands);
SHOW_EXPR(pp)
SHOW_EXPR(posOffset())
SHOW_EXPR(checksum)
SHOW_EXPR(size_t(&s1))
CHECK (posOffset() - pp >= sizeof(string) + 2*sizeof(void*));
CHECK (size_t(&s1) - slot(1) == 2*sizeof(void*)); // again the Destructor frame is placed immediately before the object
auto dtor2 = (Dtor*)slot(1); // and it has been prepended to the destructors-list in current extent
SHOW_EXPR(size_t(dtor2->next))
CHECK (dtor2->next == dtor); // with the destructor of o2 hooked up behind
CHECK (dtor->next == nullptr);
}
SHOW_EXPR(checksum)
CHECK (checksum == markSum);
}

View file

@ -81716,7 +81716,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1715990297583" ID="ID_389684421" MODIFIED="1715990309548" TEXT="direkt per Standard-Allocator belegen">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1715990310261" ID="ID_1768477879" MODIFIED="1715990332985" TEXT="Idee: stattdessen virtuell eine Extent-Struktur mit Linked-Elements erzeugen">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#42646b" CREATED="1715990310261" ID="ID_1768477879" MODIFIED="1716657839389" TEXT="Idee: stattdessen virtuell eine Extent-Struktur mit Linked-Elements erzeugen">
<icon BUILTIN="idea"/>
<node CREATED="1715991737062" ID="ID_690246579" MODIFIED="1715991797332" TEXT="Zweck: absolutes Minimum an Overhead">
<richcontent TYPE="NOTE"><html>
@ -81754,8 +81754,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="clanbomber"/>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1716130707355" ID="ID_596338208" MODIFIED="1716130715686" TEXT="Korrektheit verifizieren...">
<icon BUILTIN="flag-pink"/>
<node COLOR="#338800" CREATED="1716130707355" ID="ID_596338208" MODIFIED="1716657825150" TEXT="Korrektheit verifizieren...">
<arrowlink COLOR="#66a9c6" DESTINATION="ID_1042988047" ENDARROW="Default" ENDINCLINATION="634;-58;" ID="Arrow_ID_1847513597" STARTARROW="None" STARTINCLINATION="1082;0;"/>
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1715820305436" ID="ID_1741026762" MODIFIED="1715820308396" TEXT="Clean-up">
@ -81768,8 +81769,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="#eef0c5" COLOR="#990000" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716604728822" TEXT="automatischer Destruktor-Aufruf">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1716130029614" ID="ID_229036433" MODIFIED="1716657674986" TEXT="automatischer Destruktor-Aufruf">
<icon BUILTIN="button_ok"/>
<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>
@ -81821,17 +81822,28 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<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?">
<node COLOR="#435e98" CREATED="1716606608657" ID="ID_1834443608" MODIFIED="1716657600316" 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">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1716606637660" ID="ID_1428982691" MODIFIED="1716657605610" 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 COLOR="#338800" CREATED="1716606714899" ID="ID_631957038" MODIFIED="1716657619321" TEXT="der Deleter-Invoker k&#xf6;nnte durch eine VTable ersetzt werden">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#31739f" CREATED="1716657620591" ID="ID_1357142766" MODIFIED="1716657653856" TEXT="puh.... viel einfacher">
<icon BUILTIN="ksmiletris"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1716657678423" ID="ID_567905569" LINK="https://en.cppreference.com/w/cpp/named_req/TrivialType" MODIFIED="1716657768484" TEXT="wird nicht verwendet bei &#xbb;trivialen Typen&#xab;">
<node CREATED="1716657717681" ID="ID_739380369" MODIFIED="1716657732347" TEXT="Skalare (numerics, pointer)"/>
<node CREATED="1716657733587" ID="ID_1787247560" MODIFIED="1716657739437" TEXT="triviale Klassen">
<node CREATED="1716657746230" ID="ID_1898446055" MODIFIED="1716657751752" TEXT="trivial kopierbar"/>
<node CREATED="1716657752197" ID="ID_1032964700" MODIFIED="1716657755873" TEXT="trivial konstruierbar"/>
</node>
<node CREATED="1716657739926" ID="ID_717834034" MODIFIED="1716657744715" TEXT="Arrays derselben"/>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1716130724127" ID="ID_727448469" MODIFIED="1716133312180" TEXT="low-level-Test">
@ -81839,14 +81851,16 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1716130730820" ID="ID_539013579" MODIFIED="1716130744964" TEXT="ich mache hier gef&#xe4;hrliche Sachen">
<icon BUILTIN="clanbomber"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130764415" ID="ID_966285930" MODIFIED="1716130831111" TEXT="explizit zu verifizieren">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1716130764415" ID="ID_966285930" MODIFIED="1716657861262" TEXT="explizit zu verifizieren">
<icon BUILTIN="yes"/>
<node CREATED="1716130773434" ID="ID_643589646" MODIFIED="1716130792420" TEXT="Destruktoren werden aufgerufen &#x27f9; Pr&#xfc;fsumme"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130797235" ID="ID_532362151" MODIFIED="1716130811753" TEXT="Pointer und Gr&#xf6;&#xdf;e werden korrekt bewegt">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1716130773434" ID="ID_643589646" MODIFIED="1716657856744" TEXT="Destruktoren werden aufgerufen &#x27f9; Pr&#xfc;fsumme">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716130812393" ID="ID_136785264" MODIFIED="1716130824328" TEXT="Connectivity der virtuellen Management-Datenstruktur">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1716130797235" ID="ID_532362151" MODIFIED="1716657668978" TEXT="Pointer und Gr&#xf6;&#xdf;e werden korrekt bewegt">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1716130812393" ID="ID_136785264" MODIFIED="1716657670466" TEXT="Connectivity der virtuellen Management-Datenstruktur">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1716130832134" ID="ID_1783945506" MODIFIED="1716133346231" TEXT="hierf&#xfc;r einen friend-Test schaffen">
@ -81936,7 +81950,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1716552190584" ID="ID_1042988047" MODIFIED="1716593224739" TEXT="einen &#xdc;berlauf provozieren">
<node COLOR="#338800" CREATED="1716552190584" ID="ID_1042988047" MODIFIED="1716657825151" TEXT="einen &#xdc;berlauf provozieren">
<linktarget COLOR="#66a9c6" DESTINATION="ID_1042988047" ENDARROW="Default" ENDINCLINATION="634;-58;" ID="Arrow_ID_1847513597" SOURCE="ID_596338208" STARTARROW="None" STARTINCLINATION="1082;0;"/>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1716605425287" ID="ID_1793345065" MODIFIED="1716605442774" TEXT="Objekt-Allokation mit registriertem Destruktor">
@ -81945,8 +81960,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<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 COLOR="#338800" CREATED="1716605489686" ID="ID_838033504" MODIFIED="1716657867989" TEXT="Verzeigerung explizit verifizieren">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716552266490" ID="ID_34007851" MODIFIED="1716552275535" TEXT="weiteren &#xdc;berlauf mit Alignment">