From e5c42e05e6748902dd1308b9ad9048fafca3aa3f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 5 Jan 2012 23:17:16 +0100 Subject: [PATCH] finish ScopedCollection (closes #877) --- src/lib/scoped-collection.hpp | 131 +++++++++++++- tests/40components.tests | 2 +- tests/lib/scoped-collection-test.cpp | 250 ++++++++++++++++++--------- 3 files changed, 294 insertions(+), 89 deletions(-) diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index 52f272c06..f3a1e313f 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -29,8 +29,35 @@ ** The storage holding all those child objects is allocated in one chunk ** and never adjusted. ** - ** - TODO: retro-fit with RefArray interface + ** \par usage patterns + ** The common ground for all usage of this container is to hold and some + ** with exclusive ownership; when the enclosing container goes out of scope, + ** all the dtors of the embedded objects will be invoked. Frequently this + ** side effect is the reason for using the container: we want to own some + ** resource handles to be available exactly as long as the managing object + ** needs and accesses them. ** + ** There are two different usage patterns for populating a ScopedCollection + ** - the "stack style" usage creates an empty container (using the one arg + ** ctor just to specify the maximum size). The storage to hold up to this + ** number of objects is (heap) allocated right away, but no objects are + ** created. Later on, individual objects are "pushed" into the collection + ** by invoking #appendNewElement() to create a new element of the default + ** type I) or #appendNew(args) to create some subtype. This way, + ** the container is being filled successively. + ** - the "RAII style" usage strives to create all of the content objects + ** right away, immediately after the memory allocation. This usage pattern + ** avoids any kind of "lifecylce state". Either the container comes up sane + ** and fully populated, or the ctor call fails and any already created + ** objects are discarded. + ** @note intentionally there is no operation to discard individual objects, + ** all you can do is to #clear() the whole container. + ** @note the container can hold instances of a subclass of the type defined + ** by the template parameter I. But you need to ensure in this case + ** that the defined buffer size for each element (2nt template parameter) + ** is sufficient to hold any of these subclass instances. This condition + ** is protected by a static assertion (compilation failure). + ** @warning when using subclasses, a virtual dtor is mandatory ** @warning deliberately \em not threadsafe ** ** @see ScopedCollection_test @@ -52,6 +79,8 @@ #include #include #include +#include +#include namespace lib { @@ -87,7 +116,7 @@ namespace lib { class ElementHolder : boost::noncopyable { - + mutable char buf_[siz]; public: @@ -107,9 +136,13 @@ namespace lib { - /** Abbreviation for placement new */ +#define TYPE_SANITY_CHECK \ + BOOST_STATIC_ASSERT ((boost::is_base_of::value || boost::is_same::value)) + + + /** Abbreviation for placement new */ #define EMBEDDED_ELEMENT_CTOR(_CTOR_CALL_) \ - BOOST_STATIC_ASSERT (siz >= sizeof(TY));\ + TYPE_SANITY_CHECK; \ return *new(&buf_) _CTOR_CALL_; \ @@ -178,6 +211,7 @@ namespace lib { EMBEDDED_ELEMENT_CTOR ( TY(a1,a2,a3,a4,a5) ) } #undef EMBEDDED_ELEMENT_CTOR +#undef TYPE_SANITY_CHECK }; @@ -197,6 +231,13 @@ namespace lib { , 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 ScopedCollection (size_t maxElements, CTOR builder) : level_(0) @@ -216,6 +257,22 @@ namespace lib { throw; } } + /* == some pre-defined Builders == */ + + class FillAll; ///< fills the ScopedCollection with default constructed I-instances + + template + class FillWith; ///< fills the ScopedCollection with default constructed TY-instances + + 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() @@ -437,5 +494,71 @@ namespace lib { + /* === Supplement: pre-defined element builders === */ + + /** \par usage + * Pass an instance of this builder functor as 2nd parameter + * to ScopedCollections's ctor. (an anonymous instance is OK). + * Using this variant of the compiler switches the collection to RAII-style: + * It will immediately try to create all the embedded objects, invoking this + * builder functor for each "slot" to hold such an embedded object. Actually, + * this "slot" is an ElementHolder instance, which provides functions for + * placement-creating objects into this embedded buffer. + */ + template + class ScopedCollection::FillAll + { + public: + void + operator() (typename ScopedCollection::ElementHolder& storage) + { + storage.template create(); + } + }; + + template + template + class ScopedCollection::FillWith + { + public: + void + operator() (typename ScopedCollection::ElementHolder& storage) + { + storage.template create(); + } + }; + + /** \par usage + * This variant allows to "pull" elements from an iterator. + * Actually, the collection will try to create each element right away, + * by invoking the copy ctor and passing the value yielded by the iterator. + * @note anything in accordance to the Lumera Forward Iterator pattern is OK. + * This rules out just passing a plain STL iterator (because these can't + * tell for themselves when they're exhausted). Use an suitable iter-adapter + * instead, e.g. by invoking lib::iter_stl::eachElm(stl_container) + */ + template + template + class ScopedCollection::PullFrom + { + IT iter_; + + typedef typename iter::TypeBinding::value_type ElementType; + + public: + PullFrom (IT source) + : iter_(source) + { } + + void + operator() (typename ScopedCollection::ElementHolder& storage) + { + storage.template create (*iter_); + ++iter_; + } + }; + + + } // namespace lib #endif diff --git a/tests/40components.tests b/tests/40components.tests index d22d69d07..b1a93d5d1 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -617,7 +617,7 @@ out: ^\.throw some exceptions... END -PLANNED "Managed Collection (I)" ScopedCollection_test < (i_+off_); - break; - - case 1: - storage.create (i_+off_, trigg_); - break; - } - ++i_; - } - }; - - /** @test using the ScopedCollection according to the RAII pattern. - * For this usage style, the collection is filled right away, during - * construction. If anything goes wrong, the whole collection is - * cleared and invalidated. Consequently there is no tangible "lifecycle" - * at the usage site. Either the collection is fully usable, or not at all. - * This requires the client to provide a functor (callback) to define - * the actual objects to be created within the ScopedCollection. These - * may as well be subclasses of the base type I, provided the general - * element storage size #siz was chosen sufficiently large to hold - * those subclass instances. - * - * This test demonstrates the most elaborate usage pattern, where - * the client provides a full blown functor object, which even - * has embedded state. But, generally speaking, anything - * exposing a suitable function call operator is acceptable. - */ - void - building_RAII_Style() - { - CHECK (0 == Dummy::checksum()); - { - int rr = rand() % 100; - int trigger = 101; - - CollD coll (6, Populator(rr, trigger)); - - CHECK (!isnil (coll)); - CHECK (6 == coll.size()); - CHECK (0 != Dummy::checksum()); - - CHECK (coll[0].acc(0) == 0 + rr); - CHECK (coll[1].acc(0) == 1 + rr + trigger); - CHECK (coll[2].acc(0) == 2 + rr); - CHECK (coll[3].acc(0) == 3 + rr + trigger); - CHECK (coll[4].acc(0) == 4 + rr); - CHECK (coll[5].acc(0) == 5 + rr + trigger); - - coll.clear(); - CHECK (0 == Dummy::checksum()); - - // Verify Error handling while in creation: - // SubDummy explodes on equal ctor parameters - // which here happens for i==7 - VERIFY_ERROR (SUBVERSIVE, CollD(10, Populator(0, 7)) ); - - // any already created object was properly destroyed - CHECK (0 == Dummy::checksum()); - - } - CHECK (0 == Dummy::checksum()); - } /** @test using the ScopedCollection to hold a variable @@ -335,9 +265,161 @@ namespace test{ } CHECK (0 == Dummy::checksum()); } + + + /** @test using the ScopedCollection according to the RAII pattern. + * For this usage style, the collection is filled right away, during + * construction. If anything goes wrong, the whole collection is + * cleared and invalidated. Consequently there is no tangible "lifecycle" + * at the usage site. Either the collection is fully usable, or not at all. + * This requires the client to provide a functor (callback) to define + * the actual objects to be created within the ScopedCollection. These + * may as well be subclasses of the base type I, provided the general + * element storage size #siz was chosen sufficiently large to hold + * those subclass instances. + * + * This test demonstrates the most elaborate usage pattern, where + * the client provides a full blown functor object #Populator, + * which even has embedded state. Generally speaking, anything + * exposing a suitable function call operator is acceptable. + */ + void + building_RAII_Style() + { + CHECK (0 == Dummy::checksum()); + { + int rr = rand() % 100; + int trigger = 101; + + CollD coll (6, Populator(rr, trigger)); + + CHECK (!isnil (coll)); + CHECK (6 == coll.size()); + CHECK (0 != Dummy::checksum()); + + CHECK (coll[0].acc(0) == 0 + rr); + CHECK (coll[1].acc(0) == 1 + rr + trigger); + CHECK (coll[2].acc(0) == 2 + rr); + CHECK (coll[3].acc(0) == 3 + rr + trigger); + CHECK (coll[4].acc(0) == 4 + rr); + CHECK (coll[5].acc(0) == 5 + rr + trigger); + + coll.clear(); + CHECK (0 == Dummy::checksum()); + + // Verify Error handling while in creation: + // SubDummy explodes on equal ctor parameters + // which here happens for i==7 + VERIFY_ERROR (SUBVERSIVE, CollD(10, Populator(0, 7)) ); + + // any already created object was properly destroyed + CHECK (0 == Dummy::checksum()); + + } + CHECK (0 == Dummy::checksum()); + } + + /** Functor to populate the Collection */ + class Populator + { + uint i_; + int off_; + int trigg_; + + public: + Populator (int baseOffset, int triggerCode) + : i_(0) + , off_(baseOffset) + , trigg_(triggerCode) + { } + + void + operator() (CollD::ElementHolder& storage) + { + switch (i_ % 2) + { + case 0: + storage.create (i_+off_); + break; + + case 1: + storage.create (i_+off_, trigg_); + break; + } + ++i_; + } + }; + + + + /** @test for using ScopedCollection in RAII style, + * several pre-defined "populators" are provided. + * The most obvious one being just to fill the + * collection with default constructed objects. + */ + void + verify_defaultPopulator() + { + CHECK (0 == Dummy::checksum()); + + CollD coll (25, CollD::FillAll() ); + + CHECK (!isnil (coll)); + CHECK (25 == coll.size()); + CHECK (0 != Dummy::checksum()); + + for (CollD::iterator ii = coll.begin(); ii; ++ii) + { + CHECK ( INSTANCEOF (Dummy, & (*ii))); + CHECK (!INSTANCEOF (SubDummy, & (*ii))); + } + } + + + void + verify_subclassPopulator() + { + CHECK (0 == Dummy::checksum()); + + CollD coll (25, CollD::FillWith() ); + + CHECK (!isnil (coll)); + CHECK (25 == coll.size()); + CHECK (0 != Dummy::checksum()); + + for (CollD::iterator ii = coll.begin(); ii; ++ii) + CHECK (INSTANCEOF (SubDummy, & (*ii))); + } + + + void + verify_iteratorPopulator() + { + typedef ScopedCollection CollI; + + CollI source (25); + for (uint i=0; i < source.capacity(); ++i) + source.appendNew(i); // holding the numbers 0..24 + + CollI coll (20, CollI::pull(source.begin())); + // this immediately pulls in the first 20 elements + CHECK (!isnil (coll)); + CHECK (20 == coll.size()); + CHECK (25 == source.size()); + + for (uint i=0; i < coll.size(); ++i) + { + CHECK (coll[i] == i ); + CHECK (coll[i] == source[i]); + } + + // note: the iterator is assumed to deliver a sufficient amount of elements + VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin()))); + } }; + LAUNCHER (ScopedCollection_test, "unit common");