From d9af3abb0f8ff3d7a83263c0a15f5d702acb5110 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 22 Mar 2018 06:53:56 +0100 Subject: [PATCH] DI: implement creating singleton from arbitrary (user provided) closure/functor/lambda this is quite an ugly feature, but I couldn't come up with any convincing argument *not* to implement it (and its low hanging fruit) --- src/lib/depend-inject.hpp | 42 ++++++-- src/lib/depend2.hpp | 12 ++- .../library/dependency-configuration-test.cpp | 97 ++++++++++++++++--- wiki/thinkPad.ichthyo.mm | 75 +++++++++++--- 4 files changed, 193 insertions(+), 33 deletions(-) diff --git a/src/lib/depend-inject.hpp b/src/lib/depend-inject.hpp index b5bde307d..2f77a0319 100644 --- a/src/lib/depend-inject.hpp +++ b/src/lib/depend-inject.hpp @@ -37,6 +37,7 @@ #include "lib/error.hpp" #include "lib/depend2.hpp" +#include "lib/meta/function.hpp" #include "lib/sync-classlock.hpp" #include @@ -83,11 +84,34 @@ namespace lib { static void useSingleton() { - __assert_compatible(); - static InstanceHolder singleton; - installFactory ([&]() + useSingleton ([]{ return new SUB{}; }); + } + + /** configure dependency-injection for type SRV to manage a subclass singleton, + * which is created lazily on demand by invoking the given builder function + * @param ctor functor to create a heap allocated instance of subclass + * @throws error::Logic (LUMIERA_ERROR_LIFECYCLE) when the default factory has already + * been invoked at the point when calling this (re)configuration function. + */ + template + static void + useSingleton(FUN&& ctor) + { + using lib::meta::_Fun; + using lib::meta::Strip; + + static_assert (_Fun::value, "Need a Lambda or Function object to create a heap allocated instance"); + + using Ret = typename _Fun::Ret; + using Sub = typename Strip::TypePlain; + + __assert_compatible(); + static_assert (std::is_pointer::value, "Function must yield a pointer to a heap allocated instance"); + + static InstanceHolder singleton; + installFactory ([ctor]() { - return singleton.buildInstance(); + return singleton.buildInstance (ctor); }); } @@ -167,12 +191,18 @@ namespace lib { public: Local() + : Local([]{ return new MOC{}; }) + { } + + template + explicit + Local (FUN&& buildInstance) { __assert_compatible(); temporarilyInstallAlternateFactory (origInstance_, origFactory_ - ,[this]() + ,[=]() { - mock_.reset(new MOC{}); + mock_.reset (buildInstance()); return mock_.get(); }); } diff --git a/src/lib/depend2.hpp b/src/lib/depend2.hpp index 7ffd69dfb..abbbd64b0 100644 --- a/src/lib/depend2.hpp +++ b/src/lib/depend2.hpp @@ -101,6 +101,13 @@ namespace lib { public: TAR* buildInstance() + { + return buildInstance ([]{ return new TAR{}; }); + } + + template + TAR* + buildInstance(FUN&& ctor) { if (instance_) throw error::Fatal("Attempt to double-create a singleton service. " @@ -108,8 +115,7 @@ namespace lib { "or runtime system is seriously broken" ,error::LUMIERA_ERROR_LIFECYCLE); - // place new instance into embedded buffer - instance_.reset (new TAR{}); + instance_.reset (ctor()); return instance_.get(); } }; @@ -119,7 +125,7 @@ namespace lib { { public: ABS* - buildInstance() + buildInstance(...) { throw error::Fatal("Attempt to create a singleton instance of an abstract class. " "Application architecture or lifecycle is seriously broken."); diff --git a/tests/library/dependency-configuration-test.cpp b/tests/library/dependency-configuration-test.cpp index f86dec4fe..f63159993 100644 --- a/tests/library/dependency-configuration-test.cpp +++ b/tests/library/dependency-configuration-test.cpp @@ -109,10 +109,10 @@ namespace test{ verify_Singleton(); verify_SubclassSingleton(); verify_expose_Service_with_Lifecycle(); - verify_customFactory(); verify_automaticReplacement(); + verify_customFactory(); - CHECK (7+5+1 == checksum); // singletons stay alive until application shutdown + CHECK (9+7+5+1 == checksum); // singletons stay alive until application shutdown } @@ -204,15 +204,6 @@ namespace test{ - /** @test instance creation can be preconfigured with a closure */ - void - verify_customFactory() - { - TODO ("implement arbitrary ctor closure"); - } - - - /** @test injecting test mocks temporarily */ void verify_automaticReplacement() @@ -311,6 +302,90 @@ namespace test{ CHECK ((1+5+7)*7 == dumm().probe() ); CHECK ((1+5+7) == checksum ); } + + + + /** @test instance creation can be preconfigured with a closure + * Both Singleton and Test-Mock creation can optionally be performed through a + * user provided Lambda or Functor. To demonstrate this, we use a `Veryspecial` local class, + * which takes an `int&` as constructor parameter -- and we create the actual instance through + * a lambda, which happens to capture a local variable by reference. + * @warning this can be dangerous; in the example demonstrated here, the created singleton instance + * continues to live until termination of the test-suite. After leaving this test function, + * it thus holds a dangling reference, pointing into stack memory.... + */ + void + verify_customFactory() + { + CHECK ((1+5+7) == checksum ); + + struct Veryspecial + : Dummy<9> + { + Veryspecial(int& ref) + : magic_{ref} + { } + + int& magic_; + + virtual int + probe() override + { + return magic_++; + } + }; + + int backdoor = 22; + + DependInject>::useSingleton ([&]{ return new Veryspecial{backdoor}; }); + + CHECK ((1+5+7) == checksum ); + CHECK ( 22 == backdoor ); + + Depend> tricky; + CHECK ((1+5+7) == checksum ); + CHECK (22 == backdoor ); + + CHECK (22 == tricky().probe()); + CHECK (23 == backdoor ); + CHECK ((1+5+7+9) == checksum ); // Veryspecial Dummy<9> subclass was created on the heap + // and will continue to live there until the testsuite terminates + backdoor = 41; + CHECK (41 == tricky().probe()); + CHECK (42 == backdoor ); + + + Depend dumm; + CHECK ((1+5+7+9)*7 == dumm().probe() ); + + {////////////////////////////////////////////////////TEST-Scope + + DependInject::Local insidious ([&]{ return new Veryspecial{backdoor}; }); + + CHECK ((1+5+7+9) == checksum ); + CHECK (not insidious); + + CHECK (42 == dumm().probe() ); + CHECK (43 == backdoor ); + CHECK ((1+5+7+9+9) == checksum ); + + CHECK (isSameObject (dumm(), *insidious)); + + CHECK (43 == tricky().probe()); + CHECK (44 == backdoor ); + + backdoor = -1; + CHECK (-1 == dumm().probe() ); + CHECK ( 0 == backdoor ); + + CHECK ((1+5+7+9+9) == checksum ); + }////////////////////////////////////////////////////(End)TEST-Scope + + CHECK ((1+5+7+9) == checksum ); + CHECK ((1+5+7+9)*7 == dumm().probe() ); + CHECK ( 0 == tricky().probe()); + CHECK (+1 == backdoor ); + } // NOTE: Veryspecial holds a dangling reference into stack memory from now on! }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 46a956f7a..53d7d1c17 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -26792,10 +26792,16 @@ - - - - + + + + + + + + + + @@ -26861,8 +26867,11 @@ - - + + + + + @@ -27049,7 +27058,7 @@ - + @@ -27219,8 +27228,8 @@ - - + + @@ -27231,8 +27240,42 @@ - - + + + + + + + + + + + + + +

+ denn nun wird das "singleton" schon ziemlich gehaltlos, +

+

+ und es ist einigermaßen undurchsichtig, wo nun die Instanz erzeugt wird. +

+

+ +

+

+ Allerdings gibt es auch kein stichhaltiges Argument, dieses Feature nicht zu implementieren. +

+

+ Es ist halt einfach nahheliegend, daß man mal eine Subklasse mit abweichenden Parametern +

+

+ konstruieren wollen könnte, und es ist von der Implementierung her "quasi geschenkt". +

+ + +
+ +
@@ -27286,8 +27329,14 @@ - - + + + + + + + +