Finish the PolymorphicValue support template
This commit is contained in:
parent
710ae8fa0f
commit
6daf14211b
2 changed files with 186 additions and 61 deletions
|
|
@ -94,6 +94,43 @@
|
|||
** Indeed, as we're just using a different meaning of the VTable, only a
|
||||
** single indirection (virtual function call) is required at runtime in
|
||||
** this case to invoke the copy ctor or assignment operator.
|
||||
** Thus, in this latter (optimal) case, the fact that PolymorphicValue allows
|
||||
** to conceal the actual implementation type comes with zero runtime overhead,
|
||||
** compared with direct usage of a family of polymorphic types (with VTable).
|
||||
**
|
||||
** \par using polymorphic value objects
|
||||
**
|
||||
** To start with, we need a situation where polymorphic treatment and type erasure
|
||||
** might be applicable. That is, we use a public API, and only that, in any client
|
||||
** code, while the concrete implementation is completely self contained. Thus, in
|
||||
** the intended use, the concrete implementation objects can be assembled once,
|
||||
** typically in a factory, and after that, no further knowledge of the actual
|
||||
** implementation type is required. All further use can be coded against
|
||||
** the exposed public API.
|
||||
**
|
||||
** Given such a situation, it might be desirable to conceal the whereabouts of
|
||||
** the implementation completely from the clients employing the generated objects.
|
||||
** For example, the actual implementation might rely on a complicated subsystem
|
||||
** with many compilation dependencies, and we don't want to expose all those
|
||||
** details on the public API.
|
||||
**
|
||||
** Now, to employ PolymorphicValue in such a situation, on the usage side (header):
|
||||
** - expose the public API, but not the implementation type of the objects
|
||||
** - define an instantiation of PolymorphicValue with this API
|
||||
** - be sure to define a hard wired size limit not to be exceeded by the
|
||||
** actual implementation objects (PolymorphicValue's ctor has an assertion
|
||||
** to verify this constraint)
|
||||
** - provide some kind of factory for the clients to get the actual polymorphic
|
||||
** value instances. Clients may then freely move and copy those objects, but
|
||||
** do not need to know anything about the actual implementation object layout
|
||||
** (it could be figured out using RTTI though)
|
||||
**
|
||||
** On the implementation side (separate compilation unit)
|
||||
** - include the definition of the PolymorphicValue instantiation (of course)
|
||||
** - define the implementation types to inherit from the public API
|
||||
** - implement the mentioned factory function, based on the static build
|
||||
** PolymorphicValue#build functions, using the actual implementation type
|
||||
** as parameter.
|
||||
**
|
||||
** @see polymorphic-value-test.cpp
|
||||
** @see opaque-holder.hpp other similar opaque inline buffer templates
|
||||
|
|
@ -107,22 +144,14 @@
|
|||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/meta/duck-detector.hpp"
|
||||
//#include "lib/bool-checkable.hpp"
|
||||
//#include "lib/access-casted.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
//#include <boost/noncopyable.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE;
|
||||
// using util::isSameObject;
|
||||
// using util::unConst;
|
||||
|
||||
|
||||
namespace polyvalue { // implementation helpers...
|
||||
namespace polyvalue { // implementation details...
|
||||
|
||||
using boost::enable_if;
|
||||
using lumiera::Yes_t;
|
||||
|
|
@ -130,8 +159,23 @@ namespace lib {
|
|||
|
||||
struct EmptyBase{ };
|
||||
|
||||
template<class IFA
|
||||
,class BA = EmptyBase
|
||||
/**
|
||||
* Interface for active support of copy operations
|
||||
* by the embedded client objects. When inserted into the
|
||||
* inheritance chain \em above the concrete implementation objects,
|
||||
* PolymorphicValue is able to perform copy operations trivially and
|
||||
* without any \c dynamic_cast and other run time overhead besides a
|
||||
* simple indirection through the VTable. To enable this support, the
|
||||
* implementation objects should inherit from \c CopySupport<Interface>
|
||||
* (where \c Interface would be the public API for all these embedded
|
||||
* implementation objects).
|
||||
* Alternatively, it's also possible to place this CopySupport API as parent
|
||||
* to the public API (it might even be completely absent, but then you'd need
|
||||
* to provide an explicit specialisation of the Traits template to tell
|
||||
* PolymorphicValue how to access the copy support functions.)
|
||||
*/
|
||||
template<class IFA ///< the common public interface of all embedded objects
|
||||
,class BA = IFA ///< direct baseclass to use for this copy support API
|
||||
>
|
||||
class CopySupport
|
||||
: public BA
|
||||
|
|
@ -144,7 +188,10 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* helper to detect presence of a function
|
||||
* to support clone operations
|
||||
*/
|
||||
template<typename T>
|
||||
class exposes_copySupportFunctions
|
||||
{
|
||||
|
|
@ -157,23 +204,38 @@ namespace lib {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* traits template to deal with
|
||||
* different ways to support copy operations.
|
||||
* Default is no support by the API and implementation types.
|
||||
* In this case, the CopySupport interface is mixed in at the
|
||||
* level of the concrete implementation class and later on
|
||||
* accessed through an \c dynamic_cast
|
||||
*/
|
||||
template <class TY, class YES = void>
|
||||
struct Trait
|
||||
{
|
||||
typedef CopySupport<TY> CopyAPI;
|
||||
typedef CopySupport<TY,EmptyBase> CopyAPI;
|
||||
enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) };
|
||||
|
||||
static CopyAPI&
|
||||
accessCopyHandlingInterface (TY& bufferContents)
|
||||
{
|
||||
REQUIRE (INSTANCEOF (CopyAPI, &bufferContents));
|
||||
return dynamic_cast<CopyAPI&> (bufferContents);
|
||||
return dynamic_cast<CopyAPI&> (bufferContents);
|
||||
}
|
||||
|
||||
typedef CopyAPI AdapterAttachment;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Special case when the embedded types support copying
|
||||
* on the API level, e.g. there is a sub-API exposing a \c cloneInto
|
||||
* function. In this case, the actual implementation classes can be
|
||||
* instantiated as-is and the copy operations can be accessed by a
|
||||
* simple \c static_cast without runtime overhead.
|
||||
*/
|
||||
template <class TY>
|
||||
struct Trait<TY, typename enable_if< exposes_copySupportFunctions<TY> >::type>
|
||||
{
|
||||
|
|
@ -185,13 +247,13 @@ namespace lib {
|
|||
accessCopyHandlingInterface (IFA& bufferContents)
|
||||
{
|
||||
REQUIRE (INSTANCEOF (CopyAPI, &bufferContents));
|
||||
return static_cast<CopyAPI&> (bufferContents);
|
||||
return static_cast<CopyAPI&> (bufferContents);
|
||||
}
|
||||
|
||||
typedef EmptyBase AdapterAttachment;
|
||||
};
|
||||
|
||||
}
|
||||
}//(End)implementation details
|
||||
|
||||
|
||||
|
||||
|
|
@ -205,7 +267,7 @@ namespace lib {
|
|||
* interface. The actual implementation object might be placed into the
|
||||
* buffer through a builder function; later, this buffer may be copied
|
||||
* and passed on without knowing the actual contained type.
|
||||
*
|
||||
*
|
||||
* For using PolymorphicValue, 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!
|
||||
|
|
@ -227,57 +289,73 @@ namespace lib {
|
|||
siz = storage + _Traits::ADMIN_OVERHEAD
|
||||
};
|
||||
|
||||
|
||||
/* === embedded object in buffer === */
|
||||
|
||||
/** Storage for embedded objects */
|
||||
mutable char buf_[siz];
|
||||
|
||||
template<class IMP>
|
||||
IMP&
|
||||
access() const
|
||||
IFA&
|
||||
accessEmbedded() const
|
||||
{
|
||||
return reinterpret_cast<IMP&> (buf_);
|
||||
return reinterpret_cast<IFA&> (buf_);
|
||||
}
|
||||
|
||||
void
|
||||
destroy()
|
||||
destroyEmbedded()
|
||||
{
|
||||
access<IFA>().~IFA();
|
||||
accessEmbedded().~IFA();
|
||||
}
|
||||
|
||||
// REQUIRE (siz >= sizeof(IMP));
|
||||
|
||||
template<class IMP>
|
||||
PolymorphicValue (IMP*)
|
||||
{
|
||||
REQUIRE (siz >= sizeof(IMP));
|
||||
new(&buf_) IMP();
|
||||
}
|
||||
|
||||
template<class IMP, typename A1>
|
||||
PolymorphicValue (IMP*, A1& a1)
|
||||
{
|
||||
REQUIRE (siz >= sizeof(IMP));
|
||||
new(&buf_) IMP (a1);
|
||||
}
|
||||
|
||||
template<class IMP, typename A1, typename A2>
|
||||
PolymorphicValue (IMP*, A1& a1, A2& a2)
|
||||
{
|
||||
REQUIRE (siz >= sizeof(IMP));
|
||||
new(&buf_) IMP (a1,a2);
|
||||
}
|
||||
|
||||
template<class IMP, typename A1, typename A2, typename A3>
|
||||
PolymorphicValue (IMP*, A1& a1, A2& a2, A3& a3)
|
||||
{
|
||||
REQUIRE (siz >= sizeof(IMP));
|
||||
new(&buf_) IMP (a1,a2,a3);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation Helper: supporting copy operations.
|
||||
* Actually instances of this Adapter template are placed
|
||||
* into the internal buffer, such that they both inherit
|
||||
* from the desired implementation type and the copy
|
||||
* support interface. The implementation of the
|
||||
* concrete copy operations is provided here
|
||||
* forwarding to the copy operations
|
||||
* of the implementation object.
|
||||
*/
|
||||
template<class IMP>
|
||||
class Adapter
|
||||
: public IMP
|
||||
, public _Traits::AdapterAttachment
|
||||
, public _Traits::AdapterAttachment // mix-in, might be empty
|
||||
{
|
||||
virtual void
|
||||
cloneInto (void* targetBuffer) const
|
||||
{
|
||||
new(targetBuffer) Adapter(*this);
|
||||
new(targetBuffer) Adapter(*this); // forward to copy ctor
|
||||
}
|
||||
|
||||
virtual void
|
||||
|
|
@ -285,11 +363,11 @@ namespace lib {
|
|||
{
|
||||
REQUIRE (INSTANCEOF (Adapter, &targetBase));
|
||||
Adapter& target = static_cast<Adapter&> (targetBase);
|
||||
target = (*this);
|
||||
target = (*this); // forward to assignment operator
|
||||
}
|
||||
|
||||
public:
|
||||
/* using default copy and assignment */
|
||||
public: /* == forwarding ctor to implementation type == */
|
||||
|
||||
Adapter() : IMP() { }
|
||||
|
||||
template<typename A1>
|
||||
|
|
@ -300,31 +378,40 @@ namespace lib {
|
|||
|
||||
template<typename A1, typename A2, typename A3>
|
||||
Adapter (A1& a1, A2& a2, A3& a3) : IMP(a1,a2,a3) { }
|
||||
|
||||
/* using default copy and assignment */
|
||||
};
|
||||
|
||||
|
||||
_CopyHandlingAdapter&
|
||||
accessHandlingInterface () const
|
||||
{
|
||||
IFA& bufferContents = access<IFA>();
|
||||
_CopyHandlingAdapter& hap = _Traits::accessCopyHandlingInterface (bufferContents);
|
||||
return hap; ////TODO cleanup unnecessary temporary
|
||||
IFA& bufferContents = accessEmbedded();
|
||||
return _Traits::accessCopyHandlingInterface (bufferContents);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
operator IFA& ()
|
||||
public: /* === PolymorphicValue public API === */
|
||||
|
||||
typedef IFA Interface;
|
||||
|
||||
operator Interface& ()
|
||||
{
|
||||
return access<IFA>();
|
||||
return accessEmbedded();
|
||||
}
|
||||
operator IFA const& () const
|
||||
operator Interface const& () const
|
||||
{
|
||||
return access<IFA>();
|
||||
return accessEmbedded();
|
||||
}
|
||||
Interface*
|
||||
operator-> () const
|
||||
{
|
||||
return &( accessEmbedded() );
|
||||
}
|
||||
|
||||
~PolymorphicValue()
|
||||
{
|
||||
destroy();
|
||||
destroyEmbedded();
|
||||
}
|
||||
|
||||
PolymorphicValue (PolymorphicValue const& o)
|
||||
|
|
@ -335,10 +422,12 @@ namespace lib {
|
|||
PolymorphicValue&
|
||||
operator= (PolymorphicValue const& o)
|
||||
{
|
||||
o.accessHandlingInterface().copyInto (this->access<IFA>());
|
||||
o.accessHandlingInterface().copyInto (this->accessEmbedded());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* === static factory functions === */
|
||||
|
||||
template<class IMP>
|
||||
static PolymorphicValue
|
||||
build ()
|
||||
|
|
@ -371,10 +460,13 @@ namespace lib {
|
|||
return PolymorphicValue (type_to_build_in_buffer, a1,a2,a3);
|
||||
}
|
||||
|
||||
|
||||
/* === support Equality by forwarding to embedded === */
|
||||
|
||||
friend bool
|
||||
operator== (PolymorphicValue const& v1, PolymorphicValue const& v2)
|
||||
{
|
||||
return v1.access<IFA>() == v2.access<IFA>();
|
||||
return v1.accessEmbedded() == v2.accessEmbedded();
|
||||
}
|
||||
friend bool
|
||||
operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2)
|
||||
|
|
|
|||
|
|
@ -24,14 +24,10 @@
|
|||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/util.hpp"
|
||||
#include "lib/util-foreach.hpp"
|
||||
|
||||
#include "lib/polymorphic-value.hpp"
|
||||
//#include "lib/bool-checkable.hpp"
|
||||
|
||||
#include <iostream>
|
||||
//#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
|
@ -39,16 +35,11 @@ namespace lib {
|
|||
namespace test{
|
||||
|
||||
using ::Test;
|
||||
// using util::isnil;
|
||||
using util::for_each;
|
||||
using util::unConst;
|
||||
using util::isSameObject;
|
||||
// using lumiera::error::LUMIERA_ERROR_INVALID;
|
||||
using lumiera::error::LUMIERA_ERROR_ASSERTION;
|
||||
|
||||
// using std::vector;
|
||||
// using std::cout;
|
||||
// using std::endl;
|
||||
|
||||
namespace { // test dummy hierarchy
|
||||
// Note: largely varying space requirements
|
||||
|
|
@ -72,15 +63,23 @@ namespace test{
|
|||
|
||||
|
||||
const uint MAX_RAND = 1000;
|
||||
const uint MAX_SIZ = sizeof(long[113]); /////////////////////TODO: using just 111 causes SEGV ---> suspect the HandlingAdapter mixin to require additional storage
|
||||
const uint MAX_ELM = 111;
|
||||
const uint MAX_SIZ = sizeof(long[MAX_ELM]);
|
||||
|
||||
/* Checksums to verify proper ctor-dtor calls and copy operations */
|
||||
long _checkSum = 0;
|
||||
long _callSum = 0;
|
||||
uint _created = 0;
|
||||
|
||||
|
||||
template<uint ii>
|
||||
struct Imp : Interface
|
||||
/**
|
||||
* Template to generate concrete implementation classes.
|
||||
* @note the generated classes vary largely in size, and
|
||||
* moreover the actual place to store the checksum
|
||||
* also depends on that size parameter.
|
||||
*/
|
||||
template<uint ii, class BASE=Interface>
|
||||
struct Imp : BASE
|
||||
{
|
||||
long localData_[ii];
|
||||
|
||||
|
|
@ -159,6 +158,7 @@ namespace test{
|
|||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************************
|
||||
* @test build a bunch of PolymorphicValue objects. Handle them like copyable
|
||||
* value objects, without knowing the exact implementation type; moreover
|
||||
|
|
@ -175,9 +175,9 @@ namespace test{
|
|||
_callSum = 0;
|
||||
_created = 0;
|
||||
|
||||
verifyBasics();
|
||||
|
||||
{
|
||||
verifyBasics();
|
||||
|
||||
TestList objs = createOpaqueValues ();
|
||||
for_each (objs, operate);
|
||||
}
|
||||
|
|
@ -207,8 +207,7 @@ namespace test{
|
|||
CHECK (elm == myLocalVal);
|
||||
|
||||
long prevSum = _callSum;
|
||||
Interface& subject = myLocalVal;
|
||||
long randVal = subject.apiFunc();
|
||||
long randVal = myLocalVal->apiFunc();
|
||||
CHECK (prevSum + randVal == _callSum);
|
||||
CHECK (elm != myLocalVal);
|
||||
|
||||
|
|
@ -223,25 +222,59 @@ namespace test{
|
|||
void
|
||||
verifyBasics()
|
||||
{
|
||||
typedef Imp<MAX_ELM> MaximumSizedImp;
|
||||
|
||||
// Standard case: no copy support by client objects
|
||||
verifyCreation_and_Copy<PolyVal, MaximumSizedImp>();
|
||||
|
||||
// Special case: client objects expose extension point for copy support
|
||||
typedef polyvalue::CopySupport<Interface> CopySupportAPI; // Copy support API declared as sub-interface
|
||||
typedef Imp<MAX_ELM,CopySupportAPI> CopySupportingImp; // insert this sub-interface between public API and Implementation
|
||||
typedef PolymorphicValue<Interface, MAX_SIZ, CopySupportAPI> OptimalPolyVal; // Make the Holder use this special attachment point
|
||||
CHECK (sizeof(OptimalPolyVal) < sizeof(PolyVal)); // results in smaller Holder and less implementation overhead
|
||||
|
||||
verifyCreation_and_Copy<OptimalPolyVal, CopySupportingImp>();
|
||||
}
|
||||
|
||||
|
||||
template<class PV,class IMP>
|
||||
void
|
||||
verifyCreation_and_Copy()
|
||||
{
|
||||
typedef PV Holder;
|
||||
typedef IMP ImpType;
|
||||
typedef typename PV::Interface Api;
|
||||
|
||||
long prevSum = _checkSum;
|
||||
uint prevCnt = _created;
|
||||
|
||||
PolyVal val = PolyVal::build<Imp<111> >();
|
||||
Holder val = Holder::template build<ImpType>();
|
||||
CHECK (prevSum+111 == _checkSum); // We got one primary ctor call
|
||||
CHECK (prevCnt+1 <= _created); // Note: usually, the compiler optimises
|
||||
CHECK (prevCnt+2 >= _created); // and skips the spurious copy-operation
|
||||
CHECK (sizeof(PolyVal) > sizeof(Imp<111>));
|
||||
Interface& embedded = val;
|
||||
CHECK (sizeof(Holder) >= sizeof(ImpType));
|
||||
Api& embedded = val;
|
||||
CHECK (isSameObject(embedded,val));
|
||||
CHECK (INSTANCEOF(Imp<111>, &embedded));
|
||||
CHECK (INSTANCEOF(ImpType, &embedded));
|
||||
|
||||
prevCnt = _created;
|
||||
Holder val2(val); // invoke copy ctor without knowing the implementation type
|
||||
embedded.apiFunc();
|
||||
CHECK (val != val2); // invoking the API function had an sideeffect on the state
|
||||
val = val2; // assignment of copy back to the original...
|
||||
CHECK (val == val2); // cancels the side effect
|
||||
|
||||
CHECK (prevCnt+1 == _created); // one new embedded instance was created by copy ctor
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
verifyOverrunProtection()
|
||||
{
|
||||
typedef Imp<MAX_ELM+1> OversizedImp;
|
||||
CHECK (MAX_SIZ < sizeof(OversizedImp));
|
||||
#if false ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT
|
||||
VERIFY_ERROR (ASSERTION, PolyVal::build<Imp<112> >() );
|
||||
VERIFY_ERROR (ASSERTION, PolyVal::build<OversizedImp>() );
|
||||
#endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue