diff --git a/src/lib/linked-elements.hpp b/src/lib/linked-elements.hpp new file mode 100644 index 000000000..de6dd0d8f --- /dev/null +++ b/src/lib/linked-elements.hpp @@ -0,0 +1,384 @@ +/* + LINKED-ELEMENTS.hpp - configurable intrusive single linked list template + + Copyright (C) Lumiera.org + 2012, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file linked-elements.hpp + ** Intrusive single linked list with optional ownership. + ** This helper template allows to attach a number of tightly integrated + ** elements with low overhead. Typically, these elements are to be attached + ** once and never changed. Optionally, elements can be created in-place using + ** a custom allocation scheme; the holder might also take ownership. These + ** variations in functionality are controlled by policy templates. + ** + ** The rationale for using this approach is + ** - variable number of elements + ** - explicit support for polymorphism + ** - no need to template the holder on the number of elements + ** - no heap allocations (contrast this to using std::vector) + ** - clear and expressive notation at the usage site + ** - the need to integrate tightly with a custom allocator + ** + ** @note + ** @warning + ** + ** @see LinkedElements_test + ** @see llist.h + ** @see ScopedCollection + ** @see itertools.hpp + */ + + +#ifndef LIB_LINKED_ELEMENTS_H +#define LIB_LINKED_ELEMENTS_H + + +#include "lib/error.hpp" +#include "lib/iter-adapter.hpp" + +//#include +//#include +//#include +//#include + + +namespace lib { + + namespace error = lumiera::error; +// using error::LUMIERA_ERROR_CAPACITY; +// using error::LUMIERA_ERROR_INDEX_BOUNDS; + + + + /** + * TODO write type comment + */ + template + < class N ///< node class or Base/Interface class for nodes + > + class LinkedElements + : boost::noncopyable + { + + public: + + + + + ~LinkedElements () + { + clear(); + } + + explicit + LinkedElements (size_t maxElements) + : level_(0) + , capacity_(maxElements) + , elements_(new ElementHolder[maxElements]) + { } + + /** creating a ScopedCollection in RAII-style: + * The embedded elements will be created immediately. + * Ctor fails in case of any error during element creation. + * @param builder functor to be invoked for each "slot". + * It gets an ElementHolder& as parameter, and should + * use this to create an object of some I-subclass + */ + template + LinkedElements (size_t maxElements, CTOR builder) + : level_(0) + , capacity_(maxElements) + , elements_(new ElementHolder[maxElements]) + { + populate_by (builder); + } + + /** variation of RAII-style: using a builder function, + * which is a member of some object. This supports the + * typical usage situation, where a manager object builds + * a ScopedCollection of some components + * @param builder member function used to create the elements + * @param instance the owning class instance, on which the + * builder member function will be invoked ("this"). + */ + template + LinkedElements (size_t maxElements, void (TY::*builder) (ElementHolder&), TY * const instance) + : level_(0) + , capacity_(maxElements) + , elements_(new ElementHolder[maxElements]) + { + populate_by (builder,instance); + } + + /* == some pre-defined Builders == */ + + template + class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT + + template + static PullFrom + pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator + { + return PullFrom (iter); + } + + void + clear() + { + REQUIRE (level_ <= capacity_, "Storage corrupted"); + + while (level_) + { + --level_; + try { + elements_[level_].destroy(); + } + ERROR_LOG_AND_IGNORE (progress, "Clean-up of element in ScopedCollection") + } + } + + + /** init all elements at once, + * invoking a builder functor for each. + * @param builder to create the individual elements + * this functor is responsible to invoke the appropriate + * ElementHolder#create function, which places a new element + * into the storage frame passed as parameter. + */ + template + void + populate_by (CTOR builder) + try { + while (level_ < capacity_) + { + ElementHolder& storageFrame (elements_[level_]); + builder (storageFrame); + ++level_; + } } + catch(...) + { + WARN (progress, "Failure while populating ScopedCollection. " + "All elements will be discarded"); + clear(); + throw; + } + + /** variation of element initialisation, + * invoking a member function of some manager object + * for each collection element to be created. + */ + template + void + populate_by (void (TY::*builder) (ElementHolder&), TY * const instance) + try { + while (level_ < capacity_) + { + ElementHolder& storageFrame (elements_[level_]); + (instance->*builder) (storageFrame); + ++level_; + } } + catch(...) + { + WARN (progress, "Failure while populating ScopedCollection. " + "All elements will be discarded"); + clear(); + throw; + } + + + + /** push a new element of default type + * to the end of this container + * @note EX_STRONG */ + I& appendNewElement() { return appendNew(); } + + + template< class TY > + TY& //_________________________________________ + appendNew () ///< add object of type TY, using 0-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(); + ++level_; + return newElm; + } + + + template< class TY + , typename A1 + > + TY& //_________________________________________ + appendNew (A1 a1) ///< add object of type TY, using 1-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(a1); + ++level_; + return newElm; + } + + + template< class TY + , typename A1 + , typename A2 + > + TY& //_________________________________________ + appendNew (A1 a1, A2 a2) ///< add object of type TY, using 2-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(a1,a2); + ++level_; + return newElm; + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + > + TY& //_________________________________________ + appendNew (A1 a1, A2 a2, A3 a3) ///< add object of type TY, using 3-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(a1,a2,a3); + ++level_; + return newElm; + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + , typename A4 + > + TY& //_________________________________________ + appendNew (A1 a1, A2 a2, A3 a3, A4 a4) ///< add object of type TY, using 4-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(a1,a2,a3,a4); + ++level_; + return newElm; + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + , typename A4 + , typename A5 + > + TY& //_________________________________________ + appendNew (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) ///< add object of type TY, using 5-arg ctor + { + __ensureSufficientCapacity(); + TY& newElm = elements_[level_].template create(a1,a2,a3,a4,a5); + ++level_; + return newElm; + } + + + + /* === Element access and iteration === */ + + I& + operator[] (size_t index) const + { + if (index < level_) + return elements_[index].accessObj(); + + throw error::Logic ("Attempt to access not (yet) existing object in ScopedCollection" + , LUMIERA_ERROR_INDEX_BOUNDS); + } + + + + typedef IterAdapter< I *, const ScopedCollection *> iterator; + typedef IterAdapter const_iterator; + + + iterator begin() { return iterator (this, _access_begin()); } + const_iterator begin() const { return const_iterator (this, _access_begin()); } + iterator end () { return iterator(); } + const_iterator end () const { return const_iterator(); } + + + size_t size () const { return level_; } + size_t capacity () const { return capacity_; } + bool empty () const { return 0 == level_; } + + + + private: + + + /* ==== internal callback API for the iterator ==== */ + + /** Iteration-logic: switch to next position + * @note assuming here that the start address of the embedded object + * coincides with the start of an array element (ElementHolder) + */ + friend void + iterNext (const ScopedCollection*, I* & pos) + { + ElementHolder* & storageLocation = reinterpret_cast (pos); + ++storageLocation; + } + + friend void + iterNext (const ScopedCollection*, const I* & pos) + { + const ElementHolder* & storageLocation = reinterpret_cast (pos); + ++storageLocation; + } + + /** Iteration-logic: detect iteration end. */ + template + friend bool + hasNext (const ScopedCollection* src, POS & pos) + { + REQUIRE (src); + if ((pos) && (pos < src->_access_end())) + return true; + else + { + pos = 0; + return false; + } } + + + I* _access_begin() const { return & elements_[0].accessObj(); } + I* _access_end() const { return & elements_[level_].accessObj(); } + + }; + + + + + /* === Supplement: pre-defined === */ + + /** \par usage + */ + + + +} // namespace lib +#endif diff --git a/src/lib/llist.h b/src/lib/llist.h index d5599a007..d81b77c00 100644 --- a/src/lib/llist.h +++ b/src/lib/llist.h @@ -33,18 +33,18 @@ * this pointers point to the node itself. Note that these pointers can never ever become NULL. * This lists are used by using one node as 'root' node where its both pointers are the head/tail pointer to the actual list. * Care needs to be taken to ensure not to apply any operations meant to be applied to data nodes to the root node. - * This way is the prefered way to use this lists. + * This way is the preferred way to use this lists. * Alternatively one can store only a chain of data nodes and use a LList pointer to point to the first item * (which might be NULL in case no data is stored). When using the 2nd approach care must be taken since most functions * below expect lists to have a root node. * * This header can be used in 2 different ways: - * 1) (prerefered) just including it provides all functions as static inlined functions. This is the default + * 1) (preferred) just including it provides all functions as static inlined functions. This is the default * 2) #define LLIST_INTERFACE before including this header gives only the declarations * #define LLIST_IMPLEMENTATION before including this header yields in definitions * this can be used to generate a library. This is currently untested and not recommended. * The rationale for using inlined functions is that most functions are very small and likely to be used in performance critical parts. - * Inlining can give a hughe performance and optimization improvement here. + * Inlining can give a huge performance and optimization improvement here. * The few functions which are slightly larger are expected to be the less common used ones, so inlining them too shouldn't be a problem either */ @@ -144,7 +144,7 @@ typedef llist ** LList_ref; /** * Iterate forward over a range. - * @param start first node to be interated + * @param start first node to be iterated * @param end node after the last node be iterated * @param node pointer to the iterated node */ @@ -155,7 +155,7 @@ typedef llist ** LList_ref; /** * Iterate backward over a range. - * @param rstart first node to be interated + * @param rstart first node to be iterated * @param rend node before the last node be iterated * @param node pointer to the iterated node */ @@ -281,7 +281,7 @@ LLIST_FUNC (unsigned llist_count (const_LList self), return cnt; ); -/* private, unlink self some any list but leaves self in a uninitialized state */ +/* private, unlink self some any list but leaves self in a uninitialised state */ LLIST_FUNC (void llist_unlink_fast_ (LList self), LList nxt = self->next, pre = self->prev; nxt->prev = pre; @@ -421,7 +421,7 @@ LLIST_FUNC (LList llist_inserbefore_range (LList self, LList start, LList end), /** * Swap a node with its next node. - * @param self node to be advaced + * @param self node to be advanced * @return self * advancing will not stop at tail, one has to check that if this is intended */ @@ -534,13 +534,13 @@ LLIST_FUNC (LList llist_get_nth_stop (LList self, int n, const_LList stop), /** - * The comparsion function function type. - * certain sort and find functions depend on a user supplied coparsion function - * @param a first operand for the comparsion - * @param b second operand for the comparsion + * The comparison function function type. + * certain sort and find functions depend on a user supplied comparison function + * @param a first operand for the comparison + * @param b second operand for the comparison * @param extra user supplied data which passed through - * @return shall return a value less than zero, zero, biggier than zero when - * a is less than, equal to, biggier than b + * @return shall return a value less than zero, zero, bigger than zero when + * a is less than, equal to, bigger than b */ typedef int (*llist_cmpfn)(const_LList a, const_LList b, void* extra); @@ -623,11 +623,11 @@ LLIST_FUNC (LList llist_ufind (LList self, const_LList templ, llist_cmpfn cmp, v /** * Find a element in a sorted list. * searches the list until it find the searched element, exits searching when found an element - * biggier than the searched one. + * bigger than the searched one. * @param self list to be searched * @param templ template for the element being searched * @param cmp function for comparing 2 nodes - * @return pointer to the found LList element or NULL if nothing foound + * @return pointer to the found LList element or NULL if nothing found */ LLIST_FUNC (LList llist_sfind (const_LList self, const_LList templ, llist_cmpfn cmp, void* extra), LLIST_FOREACH(self, node) diff --git a/tests/40components.tests b/tests/40components.tests index 4f60cf263..d55ef1507 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -532,6 +532,11 @@ out: can_IterForEach : Yes END +PLANNED "Linked iterable elements" LinkedElements_test < + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" + +#include "lib/linked-elements.hpp" +#include "lib/test/testdummy.hpp" + +//#include + + +namespace lib { +namespace test{ + + namespace error = lumiera::error; + + + namespace { // test data... + + const uint NUM_ELEMENTS = 500; + + LUMIERA_ERROR_DEFINE(SUBVERSIVE, "undercover action"); + + class Nummy + : public Dummy + { + + public: + Nummy* next; + + Nummy() + : Dummy() + , next(0) + { } + + explicit + Nummy (int i) + : Dummy(i) + , next(0) + { } + }; + + + inline uint + sum (uint n) + { + return n*(n+1) / 2; + } + + }//(End) subversive test data + + + + + using util::isnil; + using util::isSameObject; +// using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + + typedef LinkedElements List; + typedef LinkedElements ListNotOwner; + + + /******************************************************************** + * @test ScopedCollection manages a fixed set of objects, but these + * child objects are noncopyable, may be polymorphic, an can + * be created either all at once or chunk wise. The API is + * similar to a vector and allows for element access + * and iteration. + */ + class LinkedElements_test : public Test + { + + virtual void + run (Arg) + { + simpleUsage(); + iterating(); + + verify_nonOwnership(); + verify_ExceptionSafety(); + populate_by_iterator(); + verify_RAII_safety(); + } + + + void + simpleUsage() + { + CHECK (0 == Dummy::checksum()); + { + List elements; + CHECK (isnil (elements)); + CHECK (0 == elements.size()); + CHECK (0 == Dummy::checksum()); + + elements.pushNew(1); + elements.pushNew(2); + elements.pushNew(3); + elements.pushNew(4); + elements.pushNew(5); + CHECK (!isnil (elements)); + CHECK (5 == elements.size()); + CHECK (0 != Dummy::checksum()); + + CHECK (Dummy::checksum() == elements[0].getVal() + + elements[1].getVal() + + elements[2].getVal() + + elements[3].getVal() + + elements[4].getVal()); + + elements.clear(); + CHECK (isnil (elements)); + CHECK (0 == elements.size()); + CHECK (0 == Dummy::checksum()); + + elements.pushNew(); + elements.pushNew(); + elements.pushNew(); + + CHECK (3 == elements.size()); + CHECK (0 != Dummy::checksum()); + } + CHECK (0 == Dummy::checksum()); + } + + + void + iterating() + { + CHECK (0 == Dummy::checksum()); + { + List elements; + for (uint i=1; i<=NUM_ELEMENTS; ++i) + elements.pushNew(i); + + // since elements where pushed, + // they should appear in reversed order + int check=NUM_ELEMENTS; + List::iterator ii = elements.begin(); + while (ii) + { + CHECK (check == ii->getVal()); + CHECK (check == ii->acc(+5) - 5); + --check; + ++ii; + } + CHECK (0 == check); + + + // Test the const iterator + List const& const_elm (elements); + check = NUM_ELEMENTS; + List::const_iterator cii = const_elm.begin(); + while (cii) + { + CHECK (check == cii->getVal()); + --check; + ++cii; + } + CHECK (0 == check); + + + // Verify correct behaviour of iteration end + CHECK (! (elements.end())); + CHECK (isnil (elements.end())); + + VERIFY_ERROR (ITER_EXHAUST, *elements.end() ); + VERIFY_ERROR (ITER_EXHAUST, ++elements.end() ); + + CHECK (ii == elements.end()); + CHECK (cii == elements.end()); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + VERIFY_ERROR (ITER_EXHAUST, ++cii ); + + } + CHECK (0 == Dummy::checksum()); + } + + + void + verify_nonOwnership() + { + CHECK (0 == Dummy::checksum()); + { + ListNotOwner elements; + CHECK (isnil (elements)); + + Num<22> n2; + Num<44> n4; + Num<66> n6; + CHECK (22+44+66 == Dummy::checksum()); + + elements.push(n2); + elements.push(n4); + elements.push(n6); + CHECK (!isnil (elements)); + CHECK (3 == elements.size()); + CHECK (22+44+66 == Dummy::checksum()); // not altered: we're referring the originals + + CHECK (66 == elements[0].getVal()); + CHECK (44 == elements[1].getVal()); + CHECK (22 == elements[2].getVal()); + CHECK (isSameObject(n2, elements[2])); + CHECK (isSameObject(n4, elements[1])); + CHECK (isSameObject(n6, elements[0])); + + elements.clear(); + CHECK (isnil (elements)); + CHECK (22+44+66 == Dummy::checksum()); // referred elements unaffected + } + CHECK (0 == Dummy::checksum()); + } + + + void + verify_ExceptionSafety() + { + CHECK (0 == Dummy::checksum()); + { + List elements; + CHECK (isnil (elements)); + + __triggerErrorAt(3); + + elements.pushNew(1); + elements.pushNew(2); + CHECK (1+2 == Dummy::checksum()); + + VERIFY_ERROR (SUBVERSIVE, elements.pushNew(3) ); + CHECK (1+2 == Dummy::checksum()); + CHECK (2 == elements.size()); + + CHECK (2 == elements[0].getVal()); + CHECK (1 == elements[1].getVal()); + + elements.clear(); + CHECK (0 == Dummy::checksum()); + __triggerError_reset(); + } + CHECK (0 == Dummy::checksum()); + } + + + void + populate_by_iterator() + { + CHECK (0 == Dummy::checksum()); + { + Populator yieldSomeElements(NUM_ELEMENTS); + List elements (yieldSomeElements); + + CHECK (!isnil (elements)); + CHECK (NUM_ELEMENTS == elements.size()); + CHECK (sum(NUM_ELEMENTS) == Dummy::checksum()); + + int check=NUM_ELEMENTS; + List::iterator ii = elements.begin(); + while (ii) + { + CHECK (check == ii->getVal()); + --check; + ++ii; + } + CHECK (0 == check); + } + CHECK (0 == Dummy::checksum()); + } + + + void + verify_RAII_safety() + { + CHECK (0 == Dummy::checksum()); + + __triggerErrorAt(3); + Populator yieldSomeElements(NUM_ELEMENTS); + VERIFY_ERROR (SUBVERSIVE, List(yieldSomeElements) ); + + CHECK (0 == Dummy::checksum()); + __triggerError_reset(); + } + + + void + verify_customAllocator() + { + CHECK (0 == Dummy::checksum()); + { + AllocationCluster allocator; + + ListCustomAllocated elements(allocator); + + elements.pushNew > (2); + elements.pushNew > (4,5); + elements.pushNew > (7,8,9); + + CHECK (sum(9) == Dummy::checksum()); + CHECK (3 == allocator.size()); + CHECK (1 == allocator.count >()); + CHECK (1 == allocator.count >()); + CHECK (1 == allocator.count >()); + + CHECK (3 == elements.size()); + CHECK (1+2 == elements[2].getVal()); + CHECK (3+4+5 == elements[1].getVal()); + CHECK (6+7+8+9 == elements[0].getVal()); + + elements.clear(); + CHECK (0 == allocator.size()); + CHECK (0 == allocator.count >()); + CHECK (0 == allocator.count >()); + CHECK (0 == allocator.count >()); + CHECK (0 == Dummy::checksum()); + } + CHECK (0 == Dummy::checksum()); + } + }; + + + + LAUNCHER (LinkedElements_test, "unit common"); + + +}} // namespace lib::test +