issue resolved, tests pass, finally (whew)

This commit is contained in:
Fischlurch 2009-07-06 02:25:19 +02:00
parent c3b8d39507
commit 2462dee5ca
4 changed files with 148 additions and 134 deletions

View file

@ -101,7 +101,11 @@ namespace typelist{
typedef lib::InPlaceAnyHolder< sizeof(FunVoid) // same size for all function objects
, lib::InPlaceAnyHolder_unrelatedTypes // no common base class!
> FunHolder;
typedef lib::InPlaceAnyHolder< sizeof(void*)
, lib::InPlaceAnyHolder_unrelatedTypes
> FunPtrHolder;
/**
* Policy for FunErasure: store an embedded tr1::function
* Using this policy allows to store arbitrary complex functor objects
@ -111,7 +115,6 @@ namespace typelist{
class StoreFunction
: public FunHolder
{
public:
template<typename SIG>
StoreFunction (SIG& fun)
@ -139,59 +142,26 @@ namespace typelist{
* The price to pay is vtable access.
*/
class StoreFunPtr
: public lib::BoolCheckable<StoreFunPtr>
: public FunPtrHolder
{
/** Helper: type erasure */
struct Holder
{
void *fP_;
virtual ~Holder() {}
};
/** storing and retrieving concrete function ptr */
template<typename SIG>
struct FunctionHolder : Holder
{
FunctionHolder (SIG *fun)
{
REQUIRE (fun);
fP_ = reinterpret_cast<void*> (fun);
}
SIG&
get()
{
return *reinterpret_cast<SIG*> (fP_);
}
};
/** embedded container holding the pointer */
Holder holder_;
public:
template<typename SIG>
StoreFunPtr (SIG& fun)
{
new(&holder_) FunctionHolder<SIG> (&fun);
}
: FunPtrHolder(&fun)
{ }
template<typename SIG>
StoreFunPtr (SIG *fun)
{
new(&holder_) FunctionHolder<SIG> (fun);
}
: FunPtrHolder(fun)
{ }
template<typename SIG>
SIG&
getFun ()
{
REQUIRE (INSTANCEOF (FunctionHolder<SIG>, &holder_));
return static_cast<FunctionHolder<SIG>&> (holder_).get();
}
bool
isValid() const
{
return holder_.fP_;
SIG *fun = get<SIG*>();
REQUIRE (fun);
return *fun;
}
};

View file

@ -29,11 +29,21 @@
** an inline buffer holding an object of the concrete subclass. Typically,
** this situation arises when dealing with functor objects.
**
** This template helps building custom objects and wrappers based on this
** pattern: it provides an buffer for the target objects and controls access
** through a two-layer capsule; while the outer container exposes a neutral
** interface, the inner container keeps track of the actual type by means
** of a vtable. OpaqueHolder can be empty; but re-accessing the concrete
** These templates help with building custom objects and wrappers based on
** this pattern: InPlaceAnyHolder provides an buffer for the target objects
** and controls access through a two-layer capsule; while the outer container
** exposes a neutral interface, the inner container keeps track of the actual
** type by means of a vtable. OpaqueHolder is built on top of InPlaceAnyHolder
** additionally to support a "common base interface" and re-access of the
** embedded object through this interface. For this to work, all of the
** stored types need to be derived from this common base interface.
** OpaqueHolder then may be even used like a smart-ptr, exposing this
** base interface. To the contrary, InPlaceAnyHolder has lesser requirements
** on the types to be stored within. It can be configured with policy classes
** to control the re-access; when using InPlaceAnyHolder_unrelatedTypes
** the individual types to be stored need not be related in any way, but
** of course this rules out anything beyond re-accessing the embedded object
** by knowing it's exact type. Generally speaking, re-accessing the concrete
** object requires knowledge of the actual type, similar to boost::any
** (but contrary to OpaqueHolder the latter uses heap storage).
**
@ -87,14 +97,15 @@ namespace lib {
}
/* ==== Policy classes for OpaqueHolder ==== */
/* ==== Policy classes controlling re-Access ==== */
/**
* Standard policy for accessing the contents via
* a common base class interface. Using this policy
* causes a virtual get() function to be used, where
* the actual Buff-subclasses will define overrides
* with covariant return types.
* causes static or dynamic casts or direct conversion
* to be employed as appropriate.
*/
template<class BA>
struct InPlaceAnyHolder_useCommonBase
@ -134,18 +145,15 @@ namespace lib {
};
/**
* Alternative policy for accessing the contents via
* Alternative policy for accessing the contents without
* a common interface; use this policy if the intention is
* to use OpaqueHolder with a family of similar classes,
* \em without requiring all of them to be derived from
* a common base class. (E.g. tr1::function objects).
* In this case, the BA template parameter of OpaqueHolder
* can be considered just a nominal placeholder.
* @note using this policy renders the smart-ptr style
* access largely useless and might even cause
* segmentation errors, when trying to access
* the contents of the buffer this way through
* a brute force reinterpret_cast.
* In this case, the "Base" type will be defined to void*
* As a consequence, we loose all type information and
* no conversions are possible on re-access. You need
* to know the \em exact type to get back at the object.
*/
struct InPlaceAnyHolder_unrelatedTypes
{
@ -166,26 +174,28 @@ namespace lib {
}
};
/////////////////////////////TODO
/////////////////////////////: separieren in einen InPlaceAnyHolder
/////////////////////////////: und als dessen Kind den OpaqueHolder
/////////////////////////////: AccessPolicy unter die konkrete Buff-Klasse einschieben
/////////////////////////////: EmptyBuffer als abgeleitet.
/**
* Inline buffer holding and owning an object while concealing the
* concrete type. Access to the contained object is similar to a
* smart-pointer, but the object isn't heap allocated. OpaqueHolder
* may be created empty, which can be checked by a bool test.
* The whole compound is copyable if and only if the contained object
* is copyable.
* concrete type. The object is given either as ctor parameter or
* by direct assignment; it is copy-constructed into the buffer.
* It is necessary to specify the required buffer storage space
* as a template parameter. InPlaceAnyHolder may be created empty
* or cleared afterwards, and this #empty() state may be detected
* at runtime. In a similar vein, when the stored object has a
* \c bool validity check, this can be accessed though #isValid().
* Moreover \code !empty() && isValid() \endcode may be tested
* as by \bool conversion of the Holder object. The whole compound
* is copyable if and only if the contained object is copyable.
*
* For using OpaqueHolder, several \b assumptions need to be fulfilled
* - any instance placed into OpaqueHolder is below the specified maximum size
* - the caller cares for thread safety. No concurrent get calls while in mutation!
* @note assertion failure when trying to place an instance not
* fitting into given size.
* @note \em not threadsafe!
*/
template
< size_t siz ///< maximum storage required for the targets to be held inline
@ -204,30 +214,14 @@ namespace lib {
void* ptr() { return &content_; }
virtual ~Buffer() {}
virtual bool isValid() const =0;
virtual bool empty() const =0;
virtual BaseP getBase() const =0;
virtual void clone (void* targetStorage) const =0;
};
template<typename TY>
struct AbstractBuff : Buffer
{
TY&
get() const ///< core operation: target is contained within the inline buffer
{
return *reinterpret_cast<TY*> (unConst(this)->ptr());
}
BaseP
getBase() const
{
return AccessPolicy::convert2base (get());
}
virtual bool isValid() const =0;
virtual bool empty() const =0;
virtual BaseP getBase() const =0;
virtual void clone (void* targetStorage) const =0;
};
/** special case: no stored object */
struct EmptyBuff : Buffer
{
virtual bool isValid() const { return false; }
@ -244,15 +238,20 @@ namespace lib {
{
new(targetStorage) EmptyBuff();
}
};
/** concrete subclass managing a specific kind of contained object.
* @note invariant: content_ always contains a valid SUB object */
* @note invariant: #content_ always contains a valid SUB object */
template<typename SUB>
struct Buff : AbstractBuff<SUB>
struct Buff : Buffer
{
SUB&
get() const ///< core operation: target is contained within the inline buffer
{
return *reinterpret_cast<SUB*> (unConst(this)->ptr());
}
~Buff()
{
get().~SUB();
@ -269,20 +268,27 @@ namespace lib {
{
new(Buffer::ptr()) SUB (oBuff.get());
}
Buff&
operator= (Buff const& ref) ///< not used currently
operator= (Buff const& ref) ///< currently not used
{
if (&ref != this)
get() = ref.get();
return *this;
}
/* == virtual access functions == */
virtual void
clone (void* targetStorage) const
{
new(targetStorage) Buff(this->get());
new(targetStorage) Buff(get());
}
virtual BaseP
getBase() const
{
return AccessPolicy::convert2base (get());
}
virtual bool
@ -299,6 +305,7 @@ namespace lib {
};
enum{ BUFFSIZE = sizeof(Buffer) };
/** embedded buffer actually holding the concrete Buff object,
@ -310,8 +317,9 @@ namespace lib {
/* === internal interface for managing the storage === */
protected:
protected: /* === internal interface for managing the storage === */
Buffer&
buff()
{
@ -324,23 +332,27 @@ namespace lib {
}
void killBuffer()
{
void
killBuffer()
{
buff().~Buffer();
}
void make_emptyBuff()
void
make_emptyBuff()
{
new(&storage_) EmptyBuff();
}
template<class SUB>
void place_inBuff (SUB const& obj)
void
place_inBuff (SUB const& obj)
{
new(&storage_) Buff<SUB> (obj);
}
void clone_inBuff (InPlaceAnyHolder const& ref)
void
clone_inBuff (InPlaceAnyHolder const& ref)
{
ref.buff().clone (storage_);
}
@ -348,6 +360,7 @@ namespace lib {
public:
~InPlaceAnyHolder()
{
killBuffer();
@ -420,8 +433,21 @@ namespace lib {
/* === re-accessing the concrete contained object === */
/** re-accessing the concrete contained object.
* When the specified type is exactly the same
* as used when storing the object, we can directly
* re-access the buffer. Otherwise, a conversion might
* be possible going through the Base type, depending
* on the actual types involved and the AccessPolicy.
* But, as we don't "know" the actual type of the object
* in storage, a \em static upcast to any type \em between
* the concrete object type and the base type has to be
* ruled out for safety reasons. When the contained object
* has RTTI, a \em dynamic cast can be performed in this
* situation. You might consider using visitor.hpp instead
* if this imposes a serious limitation.
* @throws lumiera::error::Logic when conversion/access fails
*/
template<class SUB>
SUB& get() const
{
@ -463,13 +489,16 @@ namespace lib {
/**
* Inline buffer holding and owning an object while concealing the
* concrete type. Access to the contained object is similar to a
* smart-pointer, but the object isn't heap allocated. OpaqueHolder
* may be created empty, which can be checked by a bool test.
* The whole compound is copyable if and only if the contained object
* is copyable.
* The whole compound is copyable if and only if the contained
* object is copyable.
*
* \par using OpaqueHolder
* OpaqueHolder instances are copyable value objects. They are created

View file

@ -211,15 +211,16 @@ namespace test {
// fabricate an unbound functor...
typedef typename BuildEmptyFunctor<HOL>::Type NoFunc;
NoFunc noFunction;
NoFunc noFunction = NoFunc();
HOL emptyHolder (noFunction);
ASSERT (!emptyHolder);
ASSERT ( h1 );
ASSERT ( h2 );
ASSERT ( h3 );
}
};

View file

@ -40,6 +40,7 @@ namespace test{
using util::isnil;
using util::for_each;
using util::isSameObject;
using lumiera::error::LUMIERA_ERROR_INVALID;
using lumiera::error::LUMIERA_ERROR_ASSERTION;
using std::vector;
@ -88,13 +89,13 @@ namespace test{
return myVal_ % 2;
}
};
/** maximum additional storage maybe wasted
* due to alignment of the contained object
* within OpaqueHolder's buffer
*/
const size_t ALLIGNMENT = sizeof(size_t);
/** maximum additional storage maybe wasted
* due to alignment of the contained object
* within OpaqueHolder's buffer
*/
const size_t ALLIGNMENT = sizeof(size_t);
}
@ -104,14 +105,14 @@ namespace test{
/**********************************************************************************
* @test use the OpaqueHolder inline buffer to handle a family of classes
* @test use the OpaqueHolder inline buffer to handle objects of a family of types
* through a common interface, without being forced to use heap storage
* or a custom allocator.
*
* @todo this test doesn't cover automatic conversions and conversions using
* RTTI from the target objects, while OpaqueHolder.tempate get() would
* allow for such conversions. This is similar to Ticket #141, and actually
* based on the same code as variant.hpp (access-casted.hpp)
*
* @todo this test doesn't cover automatic conversions and conversions using RTTI
* from the target objects, while \code OpaqueHolder.template get() \endcode
* would allow for such conversions. This is similar to Ticket #141, and
* actually based on the same code as variant.hpp (access-casted.hpp)
*/
class OpaqueHolder_test : public Test
{
@ -150,6 +151,9 @@ namespace test{
}
/** @test cover the basic situations of object handling,
* especially copy operations and re-assignments
*/
void
checkHandling (TestList& objs)
{
@ -164,25 +168,28 @@ namespace test{
typedef DD<3> D3;
typedef DD<5> D5;
D3 d3 (oo.get<D3>() );
ASSERT (3 == oo->getIt());
ASSERT (3 == oo->getIt()); // re-access through Base interface
ASSERT (!isSameObject (d3, *oo));
VERIFY_ERROR (WRONG_TYPE, oo.get<D5>() );
oo = D5(); // direct assignment of target into Buffer
// direct assignment of target into Buffer
oo = D5();
ASSERT (oo);
ASSERT (5 == oo->getIt());
VERIFY_ERROR (WRONG_TYPE, oo.get<D3>() );
D5 &rd5 (oo.get<D5>()); // can get a direct reference to contained object
// can get a direct reference to contained object
D5 &rd5 (oo.get<D5>());
ASSERT (isSameObject (rd5, *oo));
ASSERT (!isnil(oo));
oo = objs[3]; // copy construction also works on non-empty object
ASSERT (7 == oo->getIt());
ASSERT (7 == rd5.getIt()); // WARNING: direct ref has been messed up through the backdoor!
// WARNING: direct ref has been messed up through the backdoor!
ASSERT (7 == rd5.getIt());
ASSERT (isSameObject (rd5, *oo));
uint cnt_before = _create_count;
oo.clear();
@ -190,7 +197,9 @@ namespace test{
oo = D5(); // direct assignment also works on empty object
ASSERT (oo);
ASSERT (5 == oo->getIt());
ASSERT (_create_count == 2 + cnt_before); // one within buff and one for the anonymous temporary D5()
ASSERT (_create_count == 2 + cnt_before);
// one within buff and one for the anonymous temporary D5()
// verify that self-assignment is properly detected...
cnt_before = _create_count;
@ -206,6 +215,11 @@ namespace test{
oo.clear();
ASSERT (!oo);
ASSERT (isnil(oo));
VERIFY_ERROR (INVALID, oo.get<D5>() );
#if false ////////////////////////////////////////////////////////TODO: restore throwing ASSERT
VERIFY_ERROR (ASSERTION, oo->getIt() );
#endif////////////////////////////////////////////////////////////
// can't access empty holder...
Opaque o1 (oo);
ASSERT (!o1);