Library: allow to reverse intrusive single linked list

Looks like we'll actually retain and use this low-level solution
in cases where we just can not afford heap allocations but need
to keep polymorphic objects close to one another in memory.

Since single linked lists are filled by prepending, it is rather
common to need the reversed order of elements for traversal,
which can be achieved in linear time.

And while we're here, we can modernise the templated emplacement functions
This commit is contained in:
Fischlurch 2023-04-20 18:53:17 +02:00
parent d341f003ca
commit 856d8a3b51
5 changed files with 193 additions and 261 deletions

View file

@ -73,7 +73,7 @@ namespace lib {
public: public:
MemoryManager(TypeInfo info) : top_(0) { reset(info); } MemoryManager(TypeInfo info) : top_(0) { reset(info); }
~MemoryManager() { purge(); } ~MemoryManager() { purge(); }
size_t size() const; size_t size() const;
@ -224,8 +224,8 @@ namespace lib {
void* void*
AllocationCluster::initiateAlloc (size_t& slot) AllocationCluster::initiateAlloc (size_t& slot)
{ {
if (!slot || slot > typeHandlers_.size() || !handler(slot) ) if (!slot or slot > typeHandlers_.size() or !handler(slot) )
return 0; // Memory manager not yet initialised return nullptr; // Memory manager not yet initialised
else else
return handler(slot)->allocate(); return handler(slot)->allocate();
} }
@ -241,7 +241,7 @@ namespace lib {
if (slot > typeHandlers_.size()) if (slot > typeHandlers_.size())
typeHandlers_.resize(slot); typeHandlers_.resize(slot);
if (!handler(slot)) if (not handler(slot))
handler(slot).reset (new MemoryManager (type)); handler(slot).reset (new MemoryManager (type));
} }
@ -290,7 +290,4 @@ namespace lib {
} // namespace lib } // namespace lib

View file

@ -53,6 +53,7 @@
#include "lib/scoped-holder.hpp" #include "lib/scoped-holder.hpp"
#include "lib/scoped-holder-transfer.hpp" #include "lib/scoped-holder-transfer.hpp"
#include <utility>
#include <vector> #include <vector>
@ -93,46 +94,14 @@ namespace lib {
public: public:
AllocationCluster (); AllocationCluster ();
~AllocationCluster () noexcept; ~AllocationCluster () noexcept;
template<class TY> template<class TY, typename...ARGS>
TY& TY&
create () create (ARGS&& ...args)
{ {
TY* obj = new(allocation<TY>()) TY(); TY* obj = new(allocation<TY>()) TY (std::forward<ARGS> (args)...);
return commit(obj);
}
template<class TY, typename P0>
TY&
create (P0& p0)
{
TY* obj = new(allocation<TY>()) TY (p0);
return commit(obj);
}
template<class TY, typename P0, typename P1>
TY&
create (P0& p0, P1& p1)
{
TY* obj = new(allocation<TY>()) TY (p0,p1);
return commit(obj);
}
template<class TY, typename P0, typename P1, typename P2>
TY&
create (P0& p0, P1& p1, P2& p2)
{
TY* obj = new(allocation<TY>()) TY (p0,p1,p2);
return commit(obj);
}
template<class TY, typename P0, typename P1, typename P2, typename P3>
TY&
create (P0& p0, P1& p1, P2& p2, P3& p3)
{
TY* obj = new(allocation<TY>()) TY (p0,p1,p2,p3);
return commit(obj); return commit(obj);
} }
@ -178,9 +147,9 @@ namespace lib {
static size_t maxTypeIDs; static size_t maxTypeIDs;
typedef ScopedPtrHolder<MemoryManager> HMemManager; using HMemManager = ScopedPtrHolder<MemoryManager>;
typedef Allocator_TransferNoncopyable<HMemManager> Allo; using Allo = Allocator_TransferNoncopyable<HMemManager>;
typedef std::vector<HMemManager,Allo> ManagerTable; using ManagerTable = std::vector<HMemManager,Allo>;
ManagerTable typeHandlers_; ///< table of active MemoryManager instances ManagerTable typeHandlers_; ///< table of active MemoryManager instances
@ -253,7 +222,7 @@ namespace lib {
setup() setup()
{ {
ClassLock<AllocationCluster> guard; ClassLock<AllocationCluster> guard;
if (!id_) if (0 == id_)
id_= ++maxTypeIDs; id_= ++maxTypeIDs;
return TypeInfo ((TY*) 0 ); return TypeInfo ((TY*) 0 );
@ -282,7 +251,7 @@ namespace lib {
AllocationCluster::allocation() AllocationCluster::allocation()
{ {
void *mem = initiateAlloc (TypeSlot<TY>::get()); void *mem = initiateAlloc (TypeSlot<TY>::get());
if (!mem) if (not mem)
mem = initiateAlloc (TypeSlot<TY>::setup(),TypeSlot<TY>::get()); mem = initiateAlloc (TypeSlot<TY>::setup(),TypeSlot<TY>::get());
ENSURE (mem); ENSURE (mem);
return mem; return mem;
@ -311,7 +280,5 @@ namespace lib {
} // namespace lib } // namespace lib
#endif #endif /*LIB_ALLOCATION_CLUSTER_H*/

View file

@ -24,7 +24,7 @@
** Intrusive single linked list with optional ownership. ** Intrusive single linked list with optional ownership.
** This helper template allows to attach a number of tightly integrated ** This helper template allows to attach a number of tightly integrated
** elements with low overhead. Typically, these elements are to be attached ** elements with low overhead. Typically, these elements are to be attached
** once and never changed. Optionally, elements can be created using a ** once and never changed. Optionally, elements can be created using a
** custom allocation scheme; the holder might also take ownership. These ** custom allocation scheme; the holder might also take ownership. These
** variations in functionality are controlled by policy templates. ** variations in functionality are controlled by policy templates.
** **
@ -37,8 +37,8 @@
** - convenient access through Lumiera Forward Iterators ** - convenient access through Lumiera Forward Iterators
** - the need to integrate tightly with a custom allocator ** - the need to integrate tightly with a custom allocator
** **
** @note this linked list container is \em intrusive and thus needs the help ** @note this linked list container is _intrusive_ and thus needs the help
** of the element type, which must provide a pointer member \c next. ** of the element type, which must *provide a pointer member* `next`.
** Consequently, each such node element can't be member in ** Consequently, each such node element can't be member in
** multiple collections at the same time ** multiple collections at the same time
** @note as usual, any iterator relies on the continued existence and ** @note as usual, any iterator relies on the continued existence and
@ -65,8 +65,7 @@
#include "lib/iter-adapter.hpp" #include "lib/iter-adapter.hpp"
#include "lib/util.hpp" #include "lib/util.hpp"
#include <boost/static_assert.hpp> #include <utility>
namespace lib { namespace lib {
@ -81,8 +80,11 @@ namespace lib {
/** /**
* Policy for LinkedElements: taking ownership * Policy for LinkedElements: taking ownership
* and possibly creating heap allocated Nodes * and possibly creating heap allocated Nodes
* @note is util::MoveOnly to enforce ownership
* on behalf of LinkedElements
*/ */
struct OwningHeapAllocated struct OwningHeapAllocated
: util::MoveOnly
{ {
typedef void* CustomAllocator; typedef void* CustomAllocator;
@ -99,69 +101,11 @@ namespace lib {
/** this policy creates new elements /** this policy creates new elements
* simply by heap allocation */ * simply by heap allocation */
template<class TY> template<class TY, typename...ARGS>
TY& TY&
create () create (ARGS&& ...args)
{ {
return *new TY(); return *new TY (std::forward<ARGS> (args)...);
}
template<class TY, typename A1>
TY& //____________________________________________
create (A1 a1) ///< create object of type TY, using 1-arg ctor
{
return *new TY(a1);
}
template< class TY
, typename A1
, typename A2
>
TY& //____________________________________________
create (A1 a1, A2 a2) ///< create object of type TY, using 2-arg ctor
{
return *new TY(a1,a2);
}
template< class TY
, typename A1
, typename A2
, typename A3
>
TY& //____________________________________________
create (A1 a1, A2 a2, A3 a3) ///< create object of type TY, using 3-arg ctor
{
return *new TY(a1,a2,a3);
}
template< class TY
, typename A1
, typename A2
, typename A3
, typename A4
>
TY& //____________________________________________
create (A1 a1, A2 a2, A3 a3, A4 a4) ///< create object of type TY, using 4-arg ctor
{
return *new TY(a1,a2,a3,a4);
}
template< class TY
, typename A1
, typename A2
, typename A3
, typename A4
, typename A5
>
TY& //____________________________________________
create (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) ///< create object of type TY, using 5-arg ctor
{
return *new TY(a1,a2,a3,a4,a5);
} }
}; };
@ -191,6 +135,13 @@ namespace lib {
{ {
/* does nothing */ /* does nothing */
} }
template<class TY, typename...ARGS>
TY&
create (ARGS&&...)
{
static_assert ("NoOwnership allocation strategy can not allocate elements");
} // ... you'll have to do that yourself (that's the whole point of using this strategy)
}; };
@ -234,55 +185,11 @@ namespace lib {
} }
template<class TY> template<class TY, typename...ARGS>
TY& //__________________________________________ TY&
create () ///< place object of type TY, by default ctor create (ARGS&& ...args)
{ {
return cluster_.create<TY>(); return cluster_.create<TY> (std::forward<ARGS> (args)...);
}
template<class TY, typename A1>
TY& //___________________________________________
create (A1 a1) ///< place object of type TY, using 1-arg ctor
{
return cluster_.create<TY> (a1);
}
template< class TY
, typename A1
, typename A2
>
TY& //___________________________________________
create (A1 a1, A2 a2) ///< place object of type TY, using 2-arg ctor
{
return cluster_.create<TY> (a1,a2);
}
template< class TY
, typename A1
, typename A2
, typename A3
>
TY& //___________________________________________
create (A1 a1, A2 a2, A3 a3) ///< place object of type TY, using 3-arg ctor
{
return cluster_.create<TY> (a1,a2,a3);
}
template< class TY
, typename A1
, typename A2
, typename A3
, typename A4
>
TY& //___________________________________________
create (A1 a1, A2 a2, A3 a3, A4 a4) ///< place object of type TY, using 4-arg ctor
{
return cluster_.create<TY> (a1,a2,a3,a4);
} }
}; };
@ -315,8 +222,7 @@ namespace lib {
, class ALO = linked_elements::OwningHeapAllocated , class ALO = linked_elements::OwningHeapAllocated
> >
class LinkedElements class LinkedElements
: util::NonCopyable : ALO
, ALO
{ {
N* head_; N* head_;
@ -332,6 +238,13 @@ namespace lib {
: head_{nullptr} : head_{nullptr}
{ } { }
LinkedElements (LinkedElements const&) = default;
LinkedElements (LinkedElements&& rr)
: head_{rr.head_}
{ // possibly transfer ownership
rr.head_ = nullptr;
}
/** @param allo custom allocator or memory manager /** @param allo custom allocator or memory manager
* to be used by the policy for creating and * to be used by the policy for creating and
* discarding of node elements * discarding of node elements
@ -349,11 +262,11 @@ namespace lib {
* causes already created elements to be destroyed * causes already created elements to be destroyed
*/ */
template<class IT> template<class IT>
LinkedElements (IT elements) LinkedElements (IT&& elements)
: head_{nullptr} : head_{nullptr}
{ {
try { try {
pushAll (elements); pushAll (std::forward<IT> (elements));
} }
catch(...) catch(...)
{ {
@ -410,75 +323,46 @@ namespace lib {
} }
template< class TY > // template< class TY =N>
TY& //_________________________________________ // TY& //_________________________________________
pushNew () ///< add object of type TY, using 0-arg ctor // emplace () ///< add object of type TY, using 0-arg ctor
// {
// return push (ALO::template create<TY>());
// }
/** prepend object of type TY, forwarding ctor args */
template<class TY =N, typename...ARGS>
TY&
emplace (ARGS&& ...args)
{ {
return push (ALO::template create<TY>()); return push (ALO::template create<TY> (std::forward<ARGS> (args)...));
} }
template< class TY
, typename A1 /**
> * Mutate the complete list to change the order of elements.
TY& //_________________________________________ * @remark since pushing prepends, elements are initially in reverse order
pushNew (A1 a1) ///< add object of type TY, using 1-arg ctor * @warning this operation invalidates iterators and has O(n) cost;
* ownership and elements themselves are not affected.
*/
LinkedElements&
reverse()
{ {
return push (ALO::template create<TY>(a1)); if (not empty())
{
N* p = head_->next;
head_->next = nullptr;
while (p)
{
N& n = *p;
p = p->next;
push (n);
} }
return *this;
} }
template< class TY
, typename A1
, typename A2
>
TY& //_________________________________________
pushNew (A1 a1, A2 a2) ///< add object of type TY, using 2-arg ctor
{
return push (ALO::template create<TY>(a1,a2));
}
template< class TY
, typename A1
, typename A2
, typename A3
>
TY& //_________________________________________
pushNew (A1 a1, A2 a2, A3 a3) ///< add object of type TY, using 3-arg ctor
{
return push (ALO::template create<TY>(a1,a2,a3));
}
template< class TY
, typename A1
, typename A2
, typename A3
, typename A4
>
TY& //_________________________________________
pushNew (A1 a1, A2 a2, A3 a3, A4 a4) ///< add object of type TY, using 4-arg ctor
{
return push (ALO::template create<TY>(a1,a2,a3,a4));
}
template< class TY
, typename A1
, typename A2
, typename A3
, typename A4
, typename A5
>
TY& //_________________________________________
pushNew (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) ///< add object of type TY, using 5-arg ctor
{
return push (ALO::template create<TY>(a1,a2,a3,a4,a5));
}
/* === Element access and iteration === */ /* === Element access and iteration === */
@ -487,13 +371,13 @@ namespace lib {
operator[] (size_t index) const operator[] (size_t index) const
{ {
N* p = head_; N* p = head_;
while (p && index) while (p and index)
{ {
p = p->next; p = p->next;
--index; --index;
} }
if (!p || index) if (!p or index)
throw error::Logic ("Attempt to access element beyond the end of LinkedElements list" throw error::Logic ("Attempt to access element beyond the end of LinkedElements list"
, LERR_(INDEX_BOUNDS)); , LERR_(INDEX_BOUNDS));
else else
@ -501,9 +385,18 @@ namespace lib {
} }
/** @warning unchecked */
N&
top() const
{
REQUIRE (head_, "NIL");
return *(unConst(this)->head_);
}
/** @warning traverses to count the elements */ /** @warning traverses to count the elements */
size_t size_t
size () const size() const
{ {
uint count=0; uint count=0;
N* p = head_; N* p = head_;
@ -517,7 +410,7 @@ namespace lib {
bool bool
empty () const empty() const
{ {
return !head_; return !head_;
} }

View file

@ -39,7 +39,7 @@
////#include "lib/time/timequant.hpp" ////#include "lib/time/timequant.hpp"
////#include "lib/format-cout.hpp" ////#include "lib/format-cout.hpp"
#include "lib/depend.hpp" #include "lib/depend.hpp"
//#include "lib/itertools.hpp" #include "lib/itertools.hpp"
//#include "lib/util-coll.hpp" //#include "lib/util-coll.hpp"
#include "lib/test/test-helper.hpp" #include "lib/test/test-helper.hpp"
//#include "lib/util.hpp" //#include "lib/util.hpp"
@ -115,8 +115,19 @@ namespace test {
} }
}; };
/// @deprecated this setup is confusing and dangerous (instance identity is ambiguous)
lib::Depend<MockDispatcherTable> mockDispatcher; lib::Depend<MockDispatcherTable> mockDispatcher;
inline auto
defineBottomProvision()
{
// return lib::singleValIterator(
// JobTicket::buildProvision (
// ));
}
}//(End)internal test helpers.... }//(End)internal test helpers....
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1221 #if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1221
@ -134,7 +145,9 @@ namespace test {
{ {
public: public:
MockJobTicket() { }; // MockJobTicket()
// : JobTicket{JobTicket::Provisions{defineBottomProvision()}}
// { };
private: private:
}; };

View file

@ -69,15 +69,15 @@ namespace test{
{ {
Nummy* next; Nummy* next;
Nummy() Nummy()
: Dummy() : Dummy()
, next(0) , next{0}
{ } { }
explicit explicit
Nummy (int i) Nummy (int i)
: Dummy(i) : Dummy{i}
, next(0) , next{0}
{ {
if (i == exception_trigger) if (i == exception_trigger)
throw error::Fatal("simulated error", LERR_(PROVOKED_FAILURE)); throw error::Fatal("simulated error", LERR_(PROVOKED_FAILURE));
@ -102,7 +102,7 @@ namespace test{
/** /**
* Helper to produce a pre-determined series * Helper to produce a pre-determined series
* of objects to populate a LinkedElements list. * of objects to populate a LinkedElements list.
* @note just happily heap allocating new instances * @note just happily heap allocating new instances
* and handing them out. The LinkedElements list * and handing them out. The LinkedElements list
@ -140,7 +140,6 @@ namespace test{
class Populator class Populator
: public NummyGenerator::iterator : public NummyGenerator::iterator
{ {
public: public:
explicit explicit
Populator (uint numElms) Populator (uint numElms)
@ -165,13 +164,13 @@ namespace test{
/// default case: ownership for heap allocated nodes /// default case: ownership for heap allocated nodes
typedef LinkedElements<Nummy> List; using List = LinkedElements<Nummy>;
/// managing existing node elements without taking ownership /// managing existing node elements without taking ownership
typedef LinkedElements<Nummy, linked_elements::NoOwnership> ListNotOwner; using ListNotOwner = LinkedElements<Nummy, linked_elements::NoOwnership>;
/// creating nodes in-place, using a custom allocator for creation and disposal /// creating nodes in-place, using a custom allocator for creation and disposal
typedef LinkedElements<Nummy, linked_elements::UseAllocationCluster> ListCustomAllocated; using ListCustomAllocated = LinkedElements<Nummy, linked_elements::UseAllocationCluster>;
@ -188,6 +187,7 @@ namespace test{
{ {
simpleUsage(); simpleUsage();
iterating(); iterating();
reverseList();
verify_nonOwnership(); verify_nonOwnership();
verify_ExceptionSafety(); verify_ExceptionSafety();
@ -207,11 +207,11 @@ namespace test{
CHECK (0 == elements.size()); CHECK (0 == elements.size());
CHECK (0 == Dummy::checksum()); CHECK (0 == Dummy::checksum());
elements.pushNew<Nummy>(1); elements.emplace<Nummy>(1);
elements.pushNew<Nummy>(2); elements.emplace<Nummy>(2);
elements.pushNew<Nummy>(3); elements.emplace<Nummy>(3);
elements.pushNew<Nummy>(4); elements.emplace<Nummy>(4);
elements.pushNew<Nummy>(5); elements.emplace<Nummy>(5);
CHECK (!isnil (elements)); CHECK (!isnil (elements));
CHECK (5 == elements.size()); CHECK (5 == elements.size());
CHECK (0 != Dummy::checksum()); CHECK (0 != Dummy::checksum());
@ -227,9 +227,9 @@ namespace test{
CHECK (0 == elements.size()); CHECK (0 == elements.size());
CHECK (0 == Dummy::checksum()); CHECK (0 == Dummy::checksum());
elements.pushNew<Nummy>(); elements.emplace<Nummy>();
elements.pushNew<Nummy>(); elements.emplace<Nummy>();
elements.pushNew<Nummy>(); elements.emplace<Nummy>();
CHECK (3 == elements.size()); CHECK (3 == elements.size());
CHECK (0 != Dummy::checksum()); CHECK (0 != Dummy::checksum());
@ -245,9 +245,9 @@ namespace test{
{ {
List elements; List elements;
for (uint i=1; i<=NUM_ELEMENTS; ++i) for (uint i=1; i<=NUM_ELEMENTS; ++i)
elements.pushNew<Nummy>(i); elements.emplace<Nummy>(i);
// since elements where pushed, // since elements where pushed,
// they should appear in reversed order // they should appear in reversed order
int check=NUM_ELEMENTS; int check=NUM_ELEMENTS;
List::iterator ii = elements.begin(); List::iterator ii = elements.begin();
@ -293,6 +293,68 @@ namespace test{
} }
void
reverseList()
{
CHECK (0 == Dummy::checksum());
{
List list;
CHECK (isnil (list));
list.reverse();
CHECK (isnil (list));
CHECK (0 == Dummy::checksum());
list.emplace<Nummy>(1);
CHECK (not isnil (list));
CHECK (1 == list[0].getVal());
CHECK (1 == Dummy::checksum());
list.reverse();
CHECK (1 == Dummy::checksum());
CHECK (1 == list[0].getVal());
CHECK (not isnil (list));
list.emplace<Nummy>(2);
CHECK (not isnil (list));
CHECK (2 == list.size());
CHECK (2 == list[0].getVal());
CHECK (2+1 == Dummy::checksum());
list.reverse();
CHECK (1+2 == Dummy::checksum());
CHECK (1 == list[0].getVal());
CHECK (2 == list.size());
list.emplace<Nummy>(3);
CHECK (3 == list.size());
CHECK (3 == list.top().getVal());
CHECK (3+1+2 == Dummy::checksum());
list.reverse();
CHECK (2 == list[0].getVal());
CHECK (1 == list[1].getVal());
CHECK (3 == list[2].getVal());
List::iterator ii = list.begin();
CHECK (2 == ii->getVal());
++ii;
CHECK (1 == ii->getVal());
++ii;
CHECK (3 == ii->getVal());
++ii;
CHECK (isnil (ii));
CHECK (2+1+3 == Dummy::checksum());
list.emplace<Nummy>(4);
CHECK (4 == list.top().getVal());
CHECK (3 == list[3].getVal());
list.reverse();
CHECK (3 == list[0].getVal());
CHECK (1 == list[1].getVal());
CHECK (2 == list[2].getVal());
CHECK (4 == list[3].getVal());
CHECK (3+1+2+4 == Dummy::checksum());
}
CHECK (0 == Dummy::checksum());
}
/** @test add some node elements to the LinkedElements list /** @test add some node elements to the LinkedElements list
* but without taking ownership or performing any * but without taking ownership or performing any
* memory management. This usage pattern is helpful * memory management. This usage pattern is helpful
@ -346,11 +408,11 @@ namespace test{
__triggerErrorAt(3); __triggerErrorAt(3);
elements.pushNew<Nummy>(1); elements.emplace<Nummy>(1);
elements.pushNew<Nummy>(2); elements.emplace<Nummy>(2);
CHECK (1+2 == Dummy::checksum()); CHECK (1+2 == Dummy::checksum());
VERIFY_ERROR (PROVOKED_FAILURE, elements.pushNew<Nummy>(3) ); VERIFY_ERROR (PROVOKED_FAILURE, elements.emplace<Nummy>(3) );
CHECK (1+2 == Dummy::checksum()); CHECK (1+2 == Dummy::checksum());
CHECK (2 == elements.size()); CHECK (2 == elements.size());
@ -420,9 +482,9 @@ namespace test{
ListCustomAllocated elements(allocator); ListCustomAllocated elements(allocator);
elements.pushNew<Num<1>> (2); elements.emplace<Num<1>> (2);
elements.pushNew<Num<3>> (4,5); elements.emplace<Num<3>> (4,5);
elements.pushNew<Num<6>> (7,8,9); elements.emplace<Num<6>> (7,8,9);
CHECK (sum(9) == Dummy::checksum()); CHECK (sum(9) == Dummy::checksum());
CHECK (3 == allocator.size()); CHECK (3 == allocator.size());