From 98d5b2962ca8c8a05e231003651fef73f62f5761 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 5 Jun 2024 23:45:30 +0200 Subject: [PATCH] Library: analyse options for passing a deleter function The fundamental decision is that we want to have a single generic front-end, meaning that we must jump dynamically into a configured deleter function. And on top of that comes the additional requirement that ''some allocators'' are in fact tied to a specific instance, while other allocators are monostate. However, we can distinguish both by probing if the allocator can be default constructed, and if a default constructed allocator is equivalent to the currently used alloctor instance. If this test fails, we must indeed maintain a single allocator instance, and (to avoid overengineering for this rather special use case) we will place this allocator instance into heap memory then, with a self-cleanup mechanism On the other hand, all monostate allocators can be handled through static trapolines. --- src/lib/several.hpp | 26 +++++- wiki/thinkPad.ichthyo.mm | 189 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 9 deletions(-) diff --git a/src/lib/several.hpp b/src/lib/several.hpp index 4fe505e50..96bc8bf72 100644 --- a/src/lib/several.hpp +++ b/src/lib/several.hpp @@ -50,7 +50,7 @@ namespace lib { { union Manager { - typedef void (*Deleter) (void*, size_t); + typedef void (*Deleter) (void*, size_t, size_t); bool unmanaged :1; Deleter deleter; @@ -89,7 +89,7 @@ namespace lib { */ template class Several - : util::MoveAssign +// : util::MoveAssign ////////////////////////////////////////OOO fundamental design mismatch with intended builder usage! { protected: using Bucket = ArrayBucket*; @@ -98,6 +98,9 @@ namespace lib { Bucket data_{nullptr}; public: + ~Several() noexcept + try { discardData(); } + ERROR_LOG_AND_IGNORE (progress, "clean-up Several data") size_t size() const @@ -131,6 +134,25 @@ namespace lib { friend auto begin (Several const& svl) { return svl.begin();} friend auto end (Several const& svl) { return svl.end(); } + + private: + void + discardData() + { + if (data_ and not data_->manager.unmanaged) + { + if (data_->manager.deleter) + { + (*data_->manager.deleter) (data_, size_, data_->spread); + } + else + { + for (size_t i=0; i

+ - + + + @@ -81849,6 +81852,135 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + +

+ günstigenfalls bleibt der Backlink dann inaktiv +

+ +
+ + + +

+ heißt: er kann zwar nicht wegfallen (weil sich das dann im Typ ausdrücken würde) — aber der Pointer bleibt dann NULL und der Container fällt auf Standard-Verhalten zurück +

+ + +
+ + + + + +

+ ....das ist ein besonders relevanter use-case, nämlich wenn wir einen Allocation-Cluster verwenden; in diesem Fall wird anfangs vom Builder der Speicher zugeteilt, und dann später einfach „fallen gelassen“. Aufgrund einer allgemeinen Lifecycle-Argumentation kann zu einem späteren Zeitpunkt der gesamte Allocation-Cluster ohne weitere Aufräum-Aktivitäten verworfen werden. Das ist wie ein Garbage-Collector ohne Garbage-Collection ☺ +

+ +
+
+
+
+ + + + + + + + + + + +

+ Das ist die Quintessenz. Hat er eine eigene Identität, so muß er eigens konstruiert werden und damit eigens verdrahtet sein — und wir brauchen genau diese Allokator-Instanz zur De-Allokation. +

+ +
+
+ + + + +

+ Zunächst einmal ist das eine logische Abschwächung, und keine Verstärkung. Daher ist diese Bedingung logisch nicht äquivalent zur vorgenannten Voraussetzung (eigene Identität). Jedoch läßt sich dieses Kriterium nachschärfen: man fordert, daß der Allokator default-Konstruierbar ist, und daß eine default-konstruierte Instanz mit dem gegebenen Allokator äquivalent ist (gemäß C++ Konventionen prüfbar durch den Vergleichsoperator). +

+

+ +

+

+ Wenn dieser Test scheitert, dann muß die Allokator-Instanz erhalten bleiben. +

+ + +
+ +
+
+ + + + + + + +

+ z.B. durch einen Meta-Allokator, wie z.B. ein statisch vorgehaltener Puffer.... +

+ +
+ + + +
+ + + +
+ + + + + + + + + + + + + + + +

+ In der Tat: siehe special-job-fun.hpp +

+ + +
+ +
+
+
+
+
@@ -81957,8 +82089,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
die C-Funktion realloc() kann zwar scheinbar „zaubern“, ist aber tatsächlich auf Hilfe vom Allokator angewiesen, insofern dieser sich intern gewisse zusätzliche Reserven sichert. Und wenn seine Resevern nicht reichen, dann wird sofort woanders alloziert und alles per memmove()  umkopiert....

- -
+
@@ -81968,8 +82099,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Tatsächlich kann std::vector() dasselbe besser machen, da er ggfs move-Konstruktoren aufrufen und außerdem als zusätzliche Heuristik die aktuelle Größe des Vektors heranziehen kann, um eine angemessene Reserve bereitzustellen; außerdem ist die Größe der Reserve direkt auf das API herausgeführt.

- -
+
@@ -82063,7 +82193,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82072,12 +82202,54 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + + + + + + + +

+ Fazit: +

+

+  entweder sie ist statisch — +

+

+  oder es ist eine selbstzerstörende Heap-Allokation +

+ + +
+ + + + + + + + + + + + +

+ weil der Several-Container selber nur den Spread kennt, aber keine konkrete Typ-Info mehr hat +

+ + +
+ +
+
@@ -82086,6 +82258,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +