diff --git a/src/lib/allocation-cluster.cpp b/src/lib/allocation-cluster.cpp index bf9e686b4..fbac18787 100644 --- a/src/lib/allocation-cluster.cpp +++ b/src/lib/allocation-cluster.cpp @@ -109,11 +109,14 @@ namespace lib { }; using Extents = lib::LinkedElements; + static_assert (sizeof(Destructors) == sizeof(void*)); + static_assert (sizeof(Extents) == sizeof(void*)); + union ManagementView { Storage storage; Extents extents; - }; + }; //Note: storage.pos and extents.head_ reside at the same location ManagementView view_; diff --git a/src/lib/allocation-cluster.hpp b/src/lib/allocation-cluster.hpp index ac9c92337..cdb69aae9 100644 --- a/src/lib/allocation-cluster.hpp +++ b/src/lib/allocation-cluster.hpp @@ -65,20 +65,6 @@ namespace lib { class AllocationCluster : util::MoveOnly { - - template - struct Allocator - { - using value_type = X; - - [[nodiscard]] X* allocate (size_t n) { return mother_->allot(n); } - void deallocate (X*, size_t) noexcept { /* rejoice */ } - - Allocator(AllocationCluster* m) : mother_{m} { } - private: - AllocationCluster* mother_; - }; - class StorageManager; /** maintaining the Allocation */ @@ -106,12 +92,9 @@ namespace lib { AllocationCluster (); ~AllocationCluster () noexcept; - template - Allocator - getAllocator() - { - return Allocator{this}; - } + /* === diagnostics === */ + size_t numExtents() const; + size_t numBytes() const; template @@ -121,10 +104,24 @@ namespace lib { TY& createDisposable (ARGS&& ...); - /* === diagnostics === */ + template + struct Allocator + { + using value_type = X; + + [[nodiscard]] X* allocate (size_t n) { return mother_->allot(n); } + void deallocate (X*, size_t) noexcept { /* rejoice */ } + + Allocator(AllocationCluster* m) : mother_{m} { } + // standard copy acceptable + template + Allocator(Allocator const& o) : mother_{o.mother_} { } + + AllocationCluster* mother_; + }; - size_t numExtents() const; - size_t numBytes() const; + template + Allocator getAllocator() { return this; } private: diff --git a/src/lib/allocator-handle.hpp b/src/lib/allocator-handle.hpp index fe9df790f..3efeb6841 100644 --- a/src/lib/allocator-handle.hpp +++ b/src/lib/allocator-handle.hpp @@ -85,27 +85,31 @@ namespace lib { * from the given base allocator * - alternatively, an attempt will be made to default-construct * the rebound allocator for the other type requested. - * @warn Both avenues for adaptation may fail, which could lead to - * compilation or runtime failures. + * @warning Both avenues for adaptation may fail, + * which could lead to compilation or runtime failure. + * @remark deliberately this class inherits from the allocator, + * allowing to exploit empty-base-optimisation, since + * usage of monostate allocators is quite common. */ template class StdFactory + : private ALO { using Allo = ALO; using AlloT = std::allocator_traits; using BaseType = typename Allo::value_type; - Allo allocator_; + Allo& baseAllocator() { return *this; } template auto - adaptAllocator (Allo const& baseAllocator) + adaptAllocator() { using XAllo = typename AlloT::template rebind_alloc; if constexpr (std::is_constructible_v) - return XAllo(allocator_); + return XAllo{baseAllocator()}; else - return XAllo(); + return XAllo{}; } template @@ -139,18 +143,18 @@ namespace lib { * all those front-end instances exchangeable. */ StdFactory (Allo allo = Allo{}) - : allocator_{std::move (allo)} + : Allo{std::move (allo)} { } template bool constexpr operator== (StdFactory const& o) const { - return allocator_ == o.allocator_; + return baseAllocator() == o.baseAllocator(); } template bool constexpr operator!= (StdFactory const& o) const { - return not (allocator_ == o.allocator_); + return not (*this == o); } @@ -161,12 +165,12 @@ namespace lib { { if constexpr (std::is_same_v) { - return construct (allocator_, std::forward(args)...); + return construct (baseAllocator(), std::forward(args)...); } else { using XAlloT = typename AlloT::template rebind_traits; - auto xAllo = adaptAllocator (allocator_); + auto xAllo = adaptAllocator(); return construct (xAllo, std::forward(args)...); } } @@ -178,12 +182,12 @@ namespace lib { { if constexpr (std::is_same_v) { - destroy (allocator_, elm); + destroy (baseAllocator(), elm); } else { using XAlloT = typename AlloT::template rebind_traits; - auto xAllo = adaptAllocator (allocator_); + auto xAllo = adaptAllocator(); destroy (xAllo, elm); } } diff --git a/src/lib/linked-elements.hpp b/src/lib/linked-elements.hpp index 9cd36c5cf..080a7420f 100644 --- a/src/lib/linked-elements.hpp +++ b/src/lib/linked-elements.hpp @@ -37,6 +37,9 @@ ** - convenient access through Lumiera Forward Iterators ** - the need to integrate tightly with a custom allocator ** + ** @note effectively, LinkedElements is „just a dressed-up pointer“; + ** under the assumption that the allocator is a _monostate_, and + ** exploiting _empty-base-optimisation,_ footprint is minimal. ** @note this linked list container is _intrusive_ and thus needs the help ** of the element type, which must *provide a pointer member* `next`. ** Consequently, each such node element can not be member in @@ -96,13 +99,14 @@ namespace lib { using CustomAllocator = ALO; }; + + /** default policy: use standard heap allocation for new elements */ template using OwningHeapAllocated = OwningAllocated>; - /** * Policy for LinkedElements: never create or destroy * any elements, only allow to add already existing nodes. @@ -133,58 +137,7 @@ namespace lib { } // ... you'll have to do that yourself (that's the whole point of using this strategy) }; - - - - -#ifdef LIB_ALLOCATION_CLUSTER_H - - /** - * Policy for LinkedElements: especially using a - * lib::AllocationCluster instance for creating - * new node elements. It is mandatory to pass - * this cluster instance to the ctor of any - * LinkedElements list using this policy - * @note AllocationCluster always de-allocates - * any created elements in one sway. - */ - struct UseAllocationCluster - { - typedef AllocationCluster& CustomAllocator; - - CustomAllocator cluster_; - - - explicit - UseAllocationCluster (CustomAllocator clu) - : cluster_(clu) - { } - - - /** while this policy indeed implies creating and owing - * new objects, the AllocationCluster doesn't support - * discarding individual instances. All created objects - * within the cluster will be bulk de-allocated - * when the cluster as such goes out of scope. - */ - void - dispose (void*) - { - /* does nothing */ - } - - - template - TY* - create (ARGS&& ...args) - { - return & cluster_.create (std::forward (args)...); - } - }; - -#endif //(End)if lib/allocation-cluster.hpp was included - - }//(END)namespace linked_elements + }//(END)namespace linked_elements (predefined policies) @@ -195,7 +148,7 @@ namespace lib { - /** + /****************************************************************************//** * Intrusive single linked list, possibly taking ownership of node elements. * Additional elements can be pushed (prepended) to the list; element access * is per index number (slow) or through an Lumiera Forward Iterator traversing @@ -205,9 +158,10 @@ namespace lib { * The allocation and ownership related behaviour is controlled by a policy class * provided as template parameter. When this policy supports creation of new * elements, these might be created and prepended in one step. + * @note with the default policy, `sizeof(LinkedElements) == sizeof(N*)` */ template - < class N ///< node class or Base/Interface class for nodes + < class N ///< node class or Base/Interface class for nodes , class ALO = linked_elements::OwningHeapAllocated > class LinkedElements @@ -218,7 +172,7 @@ namespace lib { public: - ~LinkedElements() + ~LinkedElements() noexcept { clear(); } @@ -244,7 +198,8 @@ namespace lib { , head_{nullptr} { } - /** creating a LinkedElements list in RAII-style: + /** + * creating a LinkedElements list in RAII-style: * @param elements iterator to provide all the elements * to be pushed into this newly created collection * @note any exception while populating the container @@ -269,7 +224,7 @@ namespace lib { /** @note EX_FREE */ void - clear() + clear() noexcept { while (head_) { @@ -304,7 +259,7 @@ namespace lib { */ template TY& - push (TY& elm) + push (TY& elm) noexcept { elm.next = head_; head_ = &elm; diff --git a/tests/library/linked-elements-test.cpp b/tests/library/linked-elements-test.cpp index 4eb8a7f19..976cd6fd1 100644 --- a/tests/library/linked-elements-test.cpp +++ b/tests/library/linked-elements-test.cpp @@ -28,7 +28,6 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/test/diagnostic-output.hpp"/////////////TODO #include "lib/util.hpp" #include "lib/allocation-cluster.hpp" @@ -158,7 +157,7 @@ namespace test{ return n*(n+1) / 2; } - }//(End) subversive test data + }//(End) test data and helpers @@ -170,9 +169,6 @@ namespace test{ /// managing existing node elements without taking ownership using ListNotOwner = LinkedElements; - /// creating nodes in-place, using a custom allocator for creation and disposal - using ListCustomAllocated = LinkedElements; - /****************************************************************//** @@ -474,21 +470,56 @@ namespace test{ } + + /** Policy to use an Allocation cluster, + * but also to invoke all object destructors */ + struct UseAllocationCluster + { + typedef AllocationCluster& CustomAllocator; + + CustomAllocator cluster_; + + UseAllocationCluster (CustomAllocator clu) + : cluster_(clu) + { } + + template + TY* + create (ARGS&& ...args) + { + return & cluster_.create (std::forward (args)...); + } + + void dispose (void*) { /* does nothing */ } + }; + + /** @test use custom allocator to create list elements + * - a dedicated policy allows to refer to an existing AllocationCluster + * and to arrange for all object destructors to be called when this + * cluster goes out of scope + * - a C++ standard allocator can also be used; as an example, again an + * AllocationCluster is used, but this time with the default adapter, + * which places objects tight and skips invocation of destructors; + * however, since the LinkedElements destructor is called, it + * walks all elements and delegates through std::allocator_traits, + * which will invoke the (virtual) base class destructors. + */ void verify_customAllocator() { CHECK (0 == Dummy::checksum()); { - AllocationCluster allocator; + AllocationCluster cluster; - ListCustomAllocated elements(allocator); + LinkedElements elements(cluster); elements.emplace> (2); elements.emplace> (4,5); elements.emplace> (7,8,9); - const size_t EXPECT = sizeof(Num<1>) + sizeof(Num<3>) + sizeof(Num<6>) + 3*2*sizeof(void*); - CHECK (EXPECT == allocator.numBytes()); + const size_t EXPECT = sizeof(Num<1>) + sizeof(Num<3>) + sizeof(Num<6>) + + 3*2*sizeof(void*); // ◁┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄overhead for storing 3 dtor-invokers + CHECK (EXPECT == cluster.numBytes()); CHECK (sum(9) == Dummy::checksum()); CHECK (3 == elements.size()); @@ -497,13 +528,34 @@ namespace test{ CHECK (6+7+8+9 == elements[0].getVal()); elements.clear(); - CHECK (EXPECT == allocator.numBytes()); + CHECK (EXPECT == cluster.numBytes()); CHECK (sum(9) == Dummy::checksum()); // note: elements won't be discarded unless // the AllocationCluster goes out of scope -SHOW_EXPR(Dummy::checksum()) } -SHOW_EXPR(Dummy::checksum()) + CHECK (0 == Dummy::checksum()); + { + // now use AllocationCluster through the default allocator adapter... + AllocationCluster cluster; + using Allo = AllocationCluster::Allocator; + using Elms = LinkedElements>; + + Elms elements{cluster.getAllocator()}; + + elements.emplace> (2); + elements.emplace> (4,5); + + const size_t EXPECT = sizeof(Num<1>) + sizeof(Num<3>); + CHECK (EXPECT == cluster.numBytes()); + CHECK (sum(5) == Dummy::checksum()); + + CHECK (2 == elements.size()); + CHECK (1+2 == elements[1].getVal()); + CHECK (3+4+5 == elements[0].getVal()); + // note: this time the destructors will be invoked + // from LinkedElements::clear(), but not from + // the destructor of AllocationCluster + } CHECK (0 == Dummy::checksum()); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 95a3bb10f..bf2057bae 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -65106,9 +65106,9 @@ - - - + + + @@ -65172,19 +65172,56 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ⟹ LinkedElements ist nicht mehr kongruent +

+

+       mit einem einfachen Pointer +

+ +
- - - + + + + + + +
@@ -81862,10 +81899,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + @@ -81877,22 +81917,22 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - - - + + + + + - - + + @@ -81944,8 +81984,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -82121,7 +82161,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82133,6 +82173,26 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + +

+ beim Umbau der Policy für LinkedElements habe ich zunächst einen Adapter geschrieben, der den std::allocator schön sauber als Member hält — nur wird dadurch dann sizeof(LinkedElements) ≡ 2 »slots«. Und damit kippt der ganze Layout-Trick weg und der Destruktor läuft Amok. +

+

+    Lessions Learned +

+ +
+ +
+
@@ -82141,7 +82201,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -82547,7 +82607,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +