diff --git a/src/lib/lazy-init.hpp b/src/lib/lazy-init.hpp index a51a61a33..58886ff4a 100644 --- a/src/lib/lazy-init.hpp +++ b/src/lib/lazy-init.hpp @@ -45,7 +45,8 @@ ** before first use. Thus, a _»trojan functor«_ is placed into this work-function, ** with the goal to activate a „trap“ on first use. This allows to invoke the actual ** initialisation, which is also configured as a functor, and which is the only part - ** the client must provide actively, to activate the mechanism. + ** the client must provide actively, to activate the mechanism. Several initialisation + ** steps can be attached consecutively, and will later be triggered in sequence. ** ** There is one _gory detail_ however: the initialisation hook needs the actual instance ** pointer valid *at the time of actual initialisation*. And since initialisation shall @@ -54,7 +55,10 @@ ** that the »trojan functor« itself is stored somehow embedded into the target object ** to be initialised. If there is a fixed distance relation in memory, then the target ** can be derived from the self-position of the functor; if this assumption is broken - ** however, memory corruption and SEGFAULT may be caused. + ** however, memory corruption and SEGFAULT might ensue. These assumptions are covered + ** by an assertion and unit tests; as long as the function and the LazyInit instance + ** are arranged in a fixed memory layout, this scheme should work. Do not place one + ** or the other into a virtual base class though. ** ** @todo 11/2023 at the moment I am just desperately trying to get a bye-product of my ** main effort into usable shape and salvage an design idea that sounded clever @@ -384,12 +388,13 @@ namespace lib { TargetFun* target = relocate (location, -FUNCTOR_PAYLOAD_OFFSET); LazyInit* self = relocate (target, -targetOffset); REQUIRE (self); + // tie storage to this (possibly recursive) call + auto storageHandle = move(self->pendingInit_); // setup target as it would be with eager init (*target) = maybeInvoke (previousInit, location); // invoke init, possibly downcast to derived *self performInit (static_cast (self)); - self->pendingInit_.reset(); // release storage - return *target; // invoked by the »Trojan« to yield first result + return *target; // back to the »Trojan« to yield first result }}; } }; diff --git a/tests/15library.tests b/tests/15library.tests index 8f4da0140..aed131076 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -439,7 +439,7 @@ out-lit: 42 END -PLANNED "Lazy init of a functor" LazyInit_test < @@ -41,28 +39,23 @@ namespace lib { namespace test{ -// using util::_Fmt; - using std::make_unique; using util::isSameObject; using lib::meta::isFunMember; + using lib::meta::disable_if; using err::LUMIERA_ERROR_LIFECYCLE; + using std::is_same; + using std::remove_reference_t; + using std::make_unique; - namespace { // policy and configuration for test... - - // - }//(End) Test config - - - /***********************************************************************************//** * @test Verify a mix-in to allow for lazy initialisation of complex infrastructure * tied to a std::function; the intention is to have a »trap« hidden in the * function itself to trigger on first use and perform the one-time - * initialisation, then finally lock the object in place. + * initialisation, then finally lock the object at a fixed place. * @see lazy-init.hpp * @see lib::RandomDraw */ @@ -344,14 +337,14 @@ namespace test{ CHECK (self); if (self->fun) // chain-up behind existing function - self->fun = [self, prevFun = move(self->fun), nextFun = move(theFun)] + self->fun = [self, prevFun=self->fun, nextFun=theFun] (int i) { return nextFun (prevFun (i)); }; else // build new function chain, inject seed from object - self->fun = [self, newFun = move(theFun)] + self->fun = [self, newFun=theFun] (int i) { return newFun (i + self->seed); // Note: binding to actual instance location @@ -366,8 +359,8 @@ namespace test{ { installInitialiser(fun, buildInit([](int){ return 0; })); } - - template + // prevent this ctor from shadowing the copy ctors //////TICKET #963 + template, LazyDemo>>> LazyDemo (FUN&& someFun) : LazyInit{MarkDisabled()} , fun{} @@ -393,51 +386,102 @@ namespace test{ * copied; they may even be assigned to existing instances, overwriting their state. * - a second given function will be chained behind the first one; this happens immediately * if the first function was already invoked (and this initialised) + * - but when however both functions are attached immediately, prior to invocation, + * then an elaborate chain of initialisers is setup behind the scenes and played back + * in definition order once lazy initialisation is triggered + * - all the intermediary state is safe to copy and move and fork + * @remark 11/2023 memory allocations were verified using lib::test::Tracker and the EventLog */ void verify_complexUsageWithCopy() { - LazyDemo d1; - CHECK (not d1.isInit()); // not initialised, since function was not invoked yet - CHECK (d1.fun); // the functor is not empty anymore, since the »trap« was installed + LazyDemo dd; + CHECK (not dd.isInit()); // not initialised, since function was not invoked yet + CHECK (dd.fun); // the functor is not empty anymore, since the »trap« was installed - d1.seed = 2; - CHECK (0 == d1.fun(22)); // d1 was default initialised and thus got the "return 0" function - CHECK (d1.isInit()); // first invocation also triggered the init-routine + dd.seed = 2; + CHECK (0 == dd.fun(22)); // d1 was default initialised and thus got the "return 0" function + CHECK (dd.isInit()); // first invocation also triggered the init-routine // is »engaged« after init and rejects move / copy - VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(d1)} ); + VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} ); - d1 = LazyDemo{[](int i) // assign a fresh copy (discarding any state in d1) + dd = LazyDemo{[](int i) // assign a fresh copy (discarding any state in d1) { return i + 1; // using a "return i+1" function }}; - CHECK (not d1.isInit()); - CHECK (d1.seed == 0); // assignment indeed erased any existing settings (seed≔2) - CHECK (d1.fun); + CHECK (not dd.isInit()); + CHECK (dd.seed == 0); // assignment indeed erased any existing settings (seed≔2) + CHECK (dd.fun); - CHECK (23 == d1.fun(22)); // new function was tied in (while also referring to self->seed) - CHECK (d1.isInit()); - d1.seed = 3; // set the seed - CHECK (26 == d1.fun(22)); // seed value is picked up dynamically + CHECK (23 == dd.fun(22)); // new function was tied in (while also referring to self->seed) + CHECK (dd.isInit()); + dd.seed = 3; // set the seed + CHECK (26 == dd.fun(22)); // seed value is picked up dynamically - VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(d1)} ); + VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} ); // attach a further function, to be chained-up - d1.attach([](int i) + dd.attach([](int i) { return i / 2; }); - CHECK (d1.isInit()); - CHECK (d1.seed == 3); - CHECK (12 == d1.fun(21)); // 21+3+1=25 / 2 - CHECK (13 == d1.fun(22)); - CHECK (13 == d1.fun(23)); - d1.seed++; - CHECK (14 == d1.fun(23)); // 23+4+1=28 / 2 - CHECK (14 == d1.fun(24)); - CHECK (15 == d1.fun(25)); + CHECK (dd.isInit()); + CHECK (dd.seed == 3); + CHECK (12 == dd.fun(21)); // 21+3+1=25 / 2 + CHECK (13 == dd.fun(22)); + CHECK (13 == dd.fun(23)); + dd.seed++; + CHECK (14 == dd.fun(23)); // 23+4+1=28 / 2 + CHECK (14 == dd.fun(24)); + CHECK (15 == dd.fun(25)); + + // ...use exactly the same configuration, + // but applied in one shot -> chained lazy-Init + dd = LazyDemo{[](int i){return i+1; }} + .attach([](int i){return i/2; }); + dd.seed = 3; + CHECK (not dd.isInit()); + CHECK (dd.seed == 3); + CHECK (dd.fun); + + CHECK (12 == dd.fun(21)); + CHECK (13 == dd.fun(22)); + CHECK (13 == dd.fun(23)); + dd.seed++; + CHECK (14 == dd.fun(23)); + CHECK (14 == dd.fun(24)); + CHECK (15 == dd.fun(25)); + + // create a nested graph of chained pending init + dd = LazyDemo{[](int i){return i+1; }}; + LazyDemo d1{dd}; + LazyDemo d2{move(dd)}; + d2.seed = 3; + d2.attach ([](int i){return i/2; }); + LazyDemo d3{d2}; + d2.attach ([](int i){return i-1; }); + + // dd was left in defunct state by the move, and thus is locked + CHECK (not dd.fun); + CHECK (dd.isInit()); + VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} ); + // this can be amended by assigning another instance not yet engaged + dd = d2; + d2.seed = 5; + std::swap (d2,d3); + std::swap (d3,d1); + // confused?? ;-) + CHECK (not dd.isInit() and dd.seed == 3); // Seed≡3 {i+1} ⟶ {i/2} ⟶ {i-1} + CHECK (not d1.isInit() and d1.seed == 5); // Seed≡5 {i+1} ⟶ {i/2} ⟶ {i-1} + CHECK (not d2.isInit() and d2.seed == 3); // Seed≡3 {i+1} ⟶ {i/2} + CHECK (not d3.isInit() and d3.seed == 0); // Seed≡0 {i+1} + + CHECK (12 == dd.fun(23)); // 23+3 +1 = 27/2 = 13 -1 = 12 + CHECK (13 == d1.fun(23)); // 23+5 +1 = 29/2 = 14 -1 = 13 + CHECK (13 == d2.fun(23)); // 23+3 +1 = 27/2 = 13 = 13 + CHECK (24 == d3.fun(23)); // 23+0 +1 = 24 } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 12d1ec4c9..1c4e20a50 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96742,7 +96742,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96751,9 +96751,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

- + @@ -97099,35 +97099,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

zur Anwendung müssen Funktoren gebunden sein

- -
+
- - + + - - - +

In die eigentliche Auswertungsfunktion kann man eine »trojanische Funktion« installieren, die etwas völlig anderes macht, nämlich die Initialisierung. Danach überschreibt sie sich selbst mit der fertig gebundenen Funktion @@ -97142,30 +97137,24 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...mit einem gewissen Grad an Type Erasure; sie haben eine bekannte, feste Größe, egal was man reinpackt, ggfs. aber verwalten sie Heap-Storage transparent

- -
+
- - - +

das ist praktisch das Gleiche, beide haben eine VTable, aber der OpaqueHolder baut darauf auf und unterstützt auch Zugriff über das Basis-Interface

- -
+
@@ -97188,22 +97177,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

init(this) ⟹ this wird fertig gemacht

- -
+
- + @@ -97217,16 +97203,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Erfahrungswert: ein »Slot« geht immer

- -
+
@@ -97245,6 +97228,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -97256,9 +97242,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

die Trap-Door muß irgendwo auf den Initialiser zugreifen, und dieser muß wiederum die vorläufige Funktion speichern. Alle diese Allokationen müssen direkt am Trap selber »aufgehängt« werden; wenn wir also den Trap verwerfen (durch Neuzuweisung), sägen wir den Ast ab, auf dem wir sitzen. Kann man machen, wenn man Flügel hat, oder sowieso schon auf dem Sprung ist @@ -97275,11 +97259,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + + @@ -97307,29 +97291,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

die Funktion ist dieses Mal ein Feld im abgeleiteten Objekt (yess!)

- -
+
- - + - + + + @@ -97365,13 +97347,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - + + - + + + + + + @@ -97381,9 +97365,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

//          CHECK (1 == invoked); @@ -97392,23 +97374,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
//          CHECK (init);

- -
+
- - - +

die neue maybeInvoke()

- -
+ @@ -97419,9 +97397,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

previousInit @@ -97430,23 +97406,35 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Details:<error reading variable: Cannot access memory at address 0x3000000030>

- -
+
- + + + + +

+ gedankenloser Schmuh : +

+

+  move (pendingInit_) ..... +

+

+  und nachher pendingInit_.reset() +

+ +
+ - - - +

      __shared_ptr(__shared_ptr&& __r) noexcept @@ -97467,8 +97455,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
      }

- -
+ @@ -97486,24 +97473,53 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+
- - - +

... kein Problem mit der Logik

- -
+
+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + @@ -97550,16 +97566,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Ergebnis: funktioniert nicht mit shared_ptr

- -
+ @@ -97602,22 +97615,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

In der Tat mache ich mir da grade selber das Leben schwer, aber die billige Lösung würde zu Verkopplung der Belange beim Client führen, insofern der Funktor-Typ Bestandteil der Signatur von LazyInit werden müßte. Das bedeutet, da es ein mix-In ist, müßte man diesen Typ vor Instatiierung bereits kennen oder spätestens im Zuge der Instantiierung irgendwie (per Metaprogramming) ermitteln. Obwohl dies komplett unnötig wäre — denn für die gewünsche Funktionalität genügt es völlig, wenn man den target-Functor (und damit die Signatur) erst in dem Moment erfährt, in dem tatsächlich ein Initialiser vorzubereiten ist.

- -
+
@@ -97644,20 +97654,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

InPlaceBuffer mißbraucht

- -
+ @@ -97669,6 +97676,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + +
@@ -97706,20 +97716,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + - + - - - +

hier besteht ein latentes semantsiches Problem: @@ -97728,8 +97736,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
lazyInit ⟹ Objekt ist erst mal noch nicht ganz initialisiert

- -
+ @@ -97737,11 +97744,11 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -97751,7 +97758,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -97778,21 +97785,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

dieses Design ist MIST

- -
+
- + @@ -97888,22 +97892,17 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

darauf kommt es jetzt auch nicht mehr an

- -
+
- - - +

Funktor ist Funktor! @@ -97912,32 +97911,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Deshalb sind sie ja stateless (und müssen auch so bleiben, also nicht mutable). Für jeweils eine Instanz wird ein Funktor einfach »verbrannt«, indem man ihn aufruft und danach das pendingInit leert. Und zwar auf *self  — der Funktor als Solcher bleibt stateless. Wenn ihn noch jemand anders referenziert, dann schön....

- -
+
- - - +

Beim Umkonfigurieren wird der pendingInit verschoben

- -
+ - - - +

und zwar aus dem LazyInit-Objekt in den Adapter hinein. Der use-count bleibt dadurch gleich.

- -
+
@@ -97950,10 +97942,157 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + +
+ + + + + + + + + + + + +

+           using lib::test::Tracker; +

+

+           auto& log = Tracker::log; +

+

+           log.clear (this); +

+

+ +

+

+           auto f1 = [t=Tracker{11}](int i){return i+1; }; +

+

+           auto f2 = [t=Tracker{22}](int i){return i/2; }; +

+

+           auto f3 = [t=Tracker{33}](int i){return i-1; }; +

+

+ ... +

+

+ ... +

+

+           cout << "____Tracker-Log_______________\n" +

+

+                << util::join(Tracker::log,      "\n") +

+

+                << "\n───╼━━━━━━━━━━━╾──────────────"<<endl; +

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

+ sondern irgend ein Speicherinhalt +

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

+ ...und zwar deshalb, weil ich wußte, daß ich über diese Zeile beim Umbau nicht nachgedacht habe... +

+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + - - -
@@ -98309,7 +98448,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -98322,8 +98461,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + +