finish ScopedCollection (closes #877)
This commit is contained in:
parent
72e8d22454
commit
e5c42e05e6
3 changed files with 294 additions and 89 deletions
|
|
@ -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<Type>(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 <boost/noncopyable.hpp>
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
#include <boost/type_traits/is_base_of.hpp>
|
||||
|
||||
|
||||
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<I,TY>::value || boost::is_same<I,TY>::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<class CTOR>
|
||||
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<typename TY>
|
||||
class FillWith; ///< fills the ScopedCollection with default constructed TY-instances
|
||||
|
||||
template<typename IT>
|
||||
class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT
|
||||
|
||||
template<typename IT>
|
||||
static PullFrom<IT>
|
||||
pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator
|
||||
{
|
||||
return PullFrom<IT> (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 I, size_t siz>
|
||||
class ScopedCollection<I,siz>::FillAll
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator() (typename ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<I>();
|
||||
}
|
||||
};
|
||||
|
||||
template<class I, size_t siz>
|
||||
template<typename TY>
|
||||
class ScopedCollection<I,siz>::FillWith
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator() (typename ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<TY>();
|
||||
}
|
||||
};
|
||||
|
||||
/** \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<class I, size_t siz>
|
||||
template<typename IT>
|
||||
class ScopedCollection<I,siz>::PullFrom
|
||||
{
|
||||
IT iter_;
|
||||
|
||||
typedef typename iter::TypeBinding<IT>::value_type ElementType;
|
||||
|
||||
public:
|
||||
PullFrom (IT source)
|
||||
: iter_(source)
|
||||
{ }
|
||||
|
||||
void
|
||||
operator() (typename ScopedCollection<I,siz>::ElementHolder& storage)
|
||||
{
|
||||
storage.template create<ElementType> (*iter_);
|
||||
++iter_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@ out: ^\.throw some exceptions...
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Managed Collection (I)" ScopedCollection_test <<END
|
||||
TEST "Managed Collection (I)" ScopedCollection_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -67,8 +67,20 @@ namespace test{
|
|||
if (trigger == getVal())
|
||||
throw new error::Fatal ("Subversive Bomb", LUMIERA_ERROR_SUBVERSIVE);
|
||||
}
|
||||
|
||||
SubDummy()
|
||||
: Dummy()
|
||||
, trigger_(-1)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
inline uint
|
||||
sum (uint n)
|
||||
{
|
||||
return n*(n+1) / 2;
|
||||
}
|
||||
|
||||
}//(End) subversive test data
|
||||
|
||||
|
||||
|
|
@ -97,6 +109,8 @@ namespace test{
|
|||
building_RAII_Style();
|
||||
building_StackStyle();
|
||||
iterating();
|
||||
verify_defaultPopulator();
|
||||
verify_iteratorPopulator();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -178,90 +192,6 @@ namespace test{
|
|||
}
|
||||
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<Dummy> (i_+off_);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
storage.create<SubDummy> (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<Dummy> (i_+off_);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
storage.create<SubDummy> (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<SubDummy>() );
|
||||
|
||||
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<uint> CollI;
|
||||
|
||||
CollI source (25);
|
||||
for (uint i=0; i < source.capacity(); ++i)
|
||||
source.appendNew<uint>(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");
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue