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
|
** The storage holding all those child objects is allocated in one chunk
|
||||||
** and never adjusted.
|
** 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
|
** @warning deliberately \em not threadsafe
|
||||||
**
|
**
|
||||||
** @see ScopedCollection_test
|
** @see ScopedCollection_test
|
||||||
|
|
@ -52,6 +79,8 @@
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <boost/scoped_array.hpp>
|
#include <boost/scoped_array.hpp>
|
||||||
#include <boost/static_assert.hpp>
|
#include <boost/static_assert.hpp>
|
||||||
|
#include <boost/type_traits/is_same.hpp>
|
||||||
|
#include <boost/type_traits/is_base_of.hpp>
|
||||||
|
|
||||||
|
|
||||||
namespace lib {
|
namespace lib {
|
||||||
|
|
@ -107,9 +136,13 @@ namespace lib {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define TYPE_SANITY_CHECK \
|
||||||
|
BOOST_STATIC_ASSERT ((boost::is_base_of<I,TY>::value || boost::is_same<I,TY>::value))
|
||||||
|
|
||||||
|
|
||||||
/** Abbreviation for placement new */
|
/** Abbreviation for placement new */
|
||||||
#define EMBEDDED_ELEMENT_CTOR(_CTOR_CALL_) \
|
#define EMBEDDED_ELEMENT_CTOR(_CTOR_CALL_) \
|
||||||
BOOST_STATIC_ASSERT (siz >= sizeof(TY));\
|
TYPE_SANITY_CHECK; \
|
||||||
return *new(&buf_) _CTOR_CALL_; \
|
return *new(&buf_) _CTOR_CALL_; \
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -178,6 +211,7 @@ namespace lib {
|
||||||
EMBEDDED_ELEMENT_CTOR ( TY(a1,a2,a3,a4,a5) )
|
EMBEDDED_ELEMENT_CTOR ( TY(a1,a2,a3,a4,a5) )
|
||||||
}
|
}
|
||||||
#undef EMBEDDED_ELEMENT_CTOR
|
#undef EMBEDDED_ELEMENT_CTOR
|
||||||
|
#undef TYPE_SANITY_CHECK
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -197,6 +231,13 @@ namespace lib {
|
||||||
, elements_(new ElementHolder[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<class CTOR>
|
template<class CTOR>
|
||||||
ScopedCollection (size_t maxElements, CTOR builder)
|
ScopedCollection (size_t maxElements, CTOR builder)
|
||||||
: level_(0)
|
: level_(0)
|
||||||
|
|
@ -216,6 +257,22 @@ namespace lib {
|
||||||
throw;
|
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
|
void
|
||||||
clear()
|
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
|
} // namespace lib
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -617,7 +617,7 @@ out: ^\.throw some exceptions...
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
||||||
PLANNED "Managed Collection (I)" ScopedCollection_test <<END
|
TEST "Managed Collection (I)" ScopedCollection_test <<END
|
||||||
return: 0
|
return: 0
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,20 @@ namespace test{
|
||||||
if (trigger == getVal())
|
if (trigger == getVal())
|
||||||
throw new error::Fatal ("Subversive Bomb", LUMIERA_ERROR_SUBVERSIVE);
|
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
|
}//(End) subversive test data
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -97,6 +109,8 @@ namespace test{
|
||||||
building_RAII_Style();
|
building_RAII_Style();
|
||||||
building_StackStyle();
|
building_StackStyle();
|
||||||
iterating();
|
iterating();
|
||||||
|
verify_defaultPopulator();
|
||||||
|
verify_iteratorPopulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -180,90 +194,6 @@ namespace test{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** 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
|
/** @test using the ScopedCollection to hold a variable
|
||||||
* and possibly increasing number of elements, within the
|
* and possibly increasing number of elements, within the
|
||||||
* fixed limits of the maximum capacity defined by the
|
* fixed limits of the maximum capacity defined by the
|
||||||
|
|
@ -335,9 +265,161 @@ namespace test{
|
||||||
}
|
}
|
||||||
CHECK (0 == Dummy::checksum());
|
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");
|
LAUNCHER (ScopedCollection_test, "unit common");
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue