- we use a GenNode element - this holds a polymorphic value known as DataCap - besides simple attribute values, this may hold collections of GenNode sub elements - a special kind of GenNode collection, the Record, is used to represent objects The purpose of this setup is to enable an external model representation which is only loosely coupled to the interndal data representation through the exchange of (tree)diff messages
567 lines
21 KiB
C++
567 lines
21 KiB
C++
/*
|
|
POLYMORPHIC-VALUE.hpp - building opaque polymorphic value objects
|
|
|
|
Copyright (C) Lumiera.org
|
|
2011, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
/** @file polymorphic-value.hpp
|
|
** A mechanism to allow for opaque polymorphic value objects.
|
|
** This template helps to overcome a problem occasionally encountered in
|
|
** C++ programming, based on the fundamental design of C++, which favours
|
|
** explicit low-level control, copying of values and strict ctor-dtor pairs.
|
|
** Many object oriented design patterns build on polymorphism, where the
|
|
** actual type of an object isn't disclosed and collaborations rely on
|
|
** common interfaces. This doesn't mix well with the emphasis the C/C++
|
|
** language puts on efficient handling of small data elements as values
|
|
** and explicit control of the used storage; indeed several of the modern
|
|
** object oriented and functional programming techniques more or less
|
|
** assume the presence of a garbage collector or similar mechanism,
|
|
** so 'objects' need just to be mentioned by reference.
|
|
**
|
|
** In C++, in order to employ many of the well known techniques, we're bound
|
|
** more or less to explicitly put the objects somewhere in heap allocated memory
|
|
** and then pass an interface pointer or reference into the actual algorithm.
|
|
** Sometimes, this hinders a design based on constant values and small descriptor
|
|
** objects used inline, thus forcing into unnecessarily complex and heavyweight
|
|
** alternatives. While it's certainly pointless to fight the fundamental nature
|
|
** of the programming language, we may try to pull some (template based) trickery
|
|
** to make polymorphic objects fit better with the handling of small copyable
|
|
** value objects. Especially, C++ gives a special meaning to passing parameters
|
|
** as \c const& -- typically constructing an anonymous temporary object conveniently
|
|
** just for passing an abstraction barrier (while the optimiser can be expected to
|
|
** remove this barrier and the accompanying nominal copy operations altogether in
|
|
** the generated code). Consequently the ability to return a polymorphic object
|
|
** from a factory or configuration function <i>by value</i> would open a lot of
|
|
** straight forward design possibilities and concise formulations.
|
|
**
|
|
** \par how to build a copyable value without knowing it's layout in detail
|
|
**
|
|
** So the goal is to build a copyable and assignable type with value semantics,
|
|
** without disclosing the actual implementation and object layout at the usage site.
|
|
** This seemingly contradictory goal can be achieved, provided that
|
|
** - the space occupied by the actual implementation object is limited,
|
|
** so it can be placed as binary data into an otherwise opaque holder buffer
|
|
** - and the actual implementation object assists with copying and cloning
|
|
** itself, observing the actual implementation data layout
|
|
**
|
|
** The PolymorphicValue template implements this idea, by exposing a copyable
|
|
** container with value semantics to the client code. On instantiation, a common
|
|
** base interface for the actual value objects needs to be provided; the resulting
|
|
** instance will be automatically convertible to this interface. Obviously this
|
|
** common interface must be an ABC or at least contain some virtual functions.
|
|
** Moreover, the PolymorphicValue container provides static builder functions,
|
|
** allowing to place a concrete instance of a subclass into the content buffer.
|
|
** After construction, the actual type of this instance will be forgotten
|
|
** (``type erasure''), but because of the embedded vtable, on access the
|
|
** proper implementation functions will be invoked.
|
|
**
|
|
** Expanding on that pattern, the copying and cloning operations of the whole
|
|
** container can be implemented by forwarding to appropriate virtual functions
|
|
** on the embedded payload (implementation) object -- the concrete implementation
|
|
** of these virtual functions can be assumed to know the real type and thus be
|
|
** able to invoke the correct copy ctor or assignment operator. For this to
|
|
** work, the interface needs to expose those copy and clone operations somehow
|
|
** as virtual functions. There are two alternatives to get at this point:
|
|
** - in the general case, the common base interface doesn't expose such operations.
|
|
** Thus we need to <i>mix in</i> an additional \em management interface; this
|
|
** can be done by \em subclassing the desired implementation type, because
|
|
** this concrete type is irrelevant after finishing the placement constructor.
|
|
** In order to re-access this management interface, so to be able to invoke
|
|
** the copy or clone operations, we need to do an elaborate re-cast operation,
|
|
** first going down to the leaf type and then back up into the mixed-in
|
|
** management interface. Basically this operation is performed by using
|
|
** a \c dynamic_cast<CopyAPI&>(bufferContents)
|
|
** - but when the used client types provide some collaboration and implement
|
|
** this management interface either directly on the API or as an immediate
|
|
** sub-interface, then this copy/management interface is located within the
|
|
** direct inheritance chain and can be reached by a simple \c static_cast.
|
|
** 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).
|
|
**
|
|
** So, how can the implementation of copy or assignment know the actual type
|
|
** to be copied? Basically we exploit the fact that the actual instance lives
|
|
** in an opaque buffer within the "outer" container. More specifically, \em we
|
|
** place it into that buffer -- thus we're able to control the actual type used.
|
|
** This way, the actual copy operations reside in an Adapter type, which lives
|
|
** at the absolute leaf end of the inheritance chain. It even inherits from
|
|
** the "implementation type" specified by the client. Thus, within the
|
|
** context of the copy operation, we know all the concrete types.
|
|
**
|
|
**
|
|
** \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
|
|
** @see lib::time::Mutation usage example
|
|
*/
|
|
|
|
|
|
#ifndef LIB_POLYMORPHIC_VALUE_H
|
|
#define LIB_POLYMORPHIC_VALUE_H
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
#include "lib/meta/duck-detector.hpp"
|
|
|
|
#include <boost/utility/enable_if.hpp>
|
|
#include <boost/static_assert.hpp>
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
|
namespace polyvalue { // implementation details...
|
|
|
|
namespace error = lumiera::error;
|
|
using boost::enable_if;
|
|
using lib::meta::Yes_t;
|
|
using lib::meta::No_t;
|
|
|
|
struct 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
|
|
{
|
|
public:
|
|
virtual ~CopySupport() { };
|
|
virtual void cloneInto (void* targetBuffer) const =0;
|
|
virtual void copyInto (IFA& targetBase) const =0;
|
|
};
|
|
|
|
/**
|
|
* A variation for limited copy support.
|
|
* Sometimes, only cloning (copy construction) of value objects is allowed,
|
|
* but not assignment of new values to existing objects. In this case, the
|
|
* concrete values can inherit from this variant of the support API.
|
|
*/
|
|
template<class BA> ///< direct baseclass to use for this clone support API
|
|
class CloneValueSupport
|
|
: public BA
|
|
{
|
|
public:
|
|
virtual ~CloneValueSupport() { };
|
|
virtual void cloneInto (void* targetBuffer) const =0;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* helper to detect presence of a function
|
|
* to support clone operations
|
|
*/
|
|
template<typename T>
|
|
class exposes_CloneFunction
|
|
{
|
|
|
|
META_DETECT_FUNCTION(void, cloneInto, (void*) const);
|
|
|
|
public:
|
|
enum{ value = HasFunSig_cloneInto<T>::value
|
|
};
|
|
};
|
|
|
|
/**
|
|
* helper to detect if the API supports only
|
|
* copy construction, but no assignment
|
|
*/
|
|
template<typename T>
|
|
class allow_Clone_but_no_Copy
|
|
{
|
|
META_DETECT_MEMBER(copyInto);
|
|
|
|
public:
|
|
enum{ value = exposes_CloneFunction<T>::value
|
|
&& ! HasMember_copyInto<T>::value
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Policy class for invoking the assignment operator.
|
|
* By default the embedded payload objects are assumed
|
|
* to be freely assignable
|
|
*/
|
|
template<class API, class YES =void>
|
|
struct AssignmentPolicy
|
|
{
|
|
template<class IMP>
|
|
static void
|
|
assignEmbedded(IMP& dest,IMP const& src)
|
|
{
|
|
dest = src;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* special case when the embedded payload objects permit
|
|
* copy construction, but no assignment to existing instances.
|
|
* Instead of invoking the assignment operator (which typically
|
|
* isn't defined at all in such cases), a misguided assignment
|
|
* to the container will raise an exception
|
|
*/
|
|
template<class API>
|
|
struct AssignmentPolicy<API, typename enable_if< allow_Clone_but_no_Copy<API> >::type>
|
|
{
|
|
template<class IMP>
|
|
static void
|
|
assignEmbedded(IMP&,IMP const&)
|
|
{
|
|
throw error::Logic("attempt to overwrite unmodifiable value");
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* 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 a \c dynamic_cast
|
|
*/
|
|
template <class TY, class YES = void>
|
|
struct Trait
|
|
{
|
|
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);
|
|
}
|
|
|
|
typedef CopyAPI AdapterAttachment;
|
|
typedef AssignmentPolicy<CopyAPI> Assignment;
|
|
};
|
|
|
|
|
|
/**
|
|
* 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_CloneFunction<TY> >::type>
|
|
{
|
|
typedef TY CopyAPI;
|
|
enum{ ADMIN_OVERHEAD = 1 * sizeof(void*) };
|
|
|
|
template<class IFA>
|
|
static CopyAPI&
|
|
accessCopyHandlingInterface (IFA& bufferContents)
|
|
{
|
|
REQUIRE (INSTANCEOF (CopyAPI, &bufferContents));
|
|
return static_cast<CopyAPI&> (bufferContents);
|
|
}
|
|
|
|
typedef EmptyBase AdapterAttachment;
|
|
typedef AssignmentPolicy<CopyAPI> Assignment;
|
|
};
|
|
|
|
}//(End)implementation details
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Template to build polymorphic value objects.
|
|
* Inline buffer with value semantics, yet holding and owning an object
|
|
* while concealing the concrete type, exposing only the public interface.
|
|
* Access to the contained object is by implicit conversion to this public
|
|
* 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!
|
|
*
|
|
* @warning when a create or copy-into operation fails with exception, the whole
|
|
* PolymorphicValue object is in undefined state and must not be used henceforth.
|
|
*/
|
|
template
|
|
< class IFA ///< the nominal Base/Interface class for a family of types
|
|
, size_t storage ///< maximum storage required for the target data to be held inline
|
|
, class CPY = IFA ///< special sub-interface to support copy operations
|
|
>
|
|
class PolymorphicValue
|
|
{
|
|
|
|
typedef polyvalue::Trait<CPY> _Traits;
|
|
typedef typename _Traits::CopyAPI _CopyHandlingAdapter;
|
|
typedef typename _Traits::Assignment _AssignmentPolicy;
|
|
enum{
|
|
siz = storage + _Traits::ADMIN_OVERHEAD
|
|
};
|
|
|
|
|
|
/* === embedded object in buffer === */
|
|
|
|
/** Storage for embedded objects */
|
|
mutable char buf_[siz];
|
|
|
|
IFA&
|
|
accessEmbedded() const
|
|
{
|
|
return reinterpret_cast<IFA&> (buf_);
|
|
}
|
|
|
|
void
|
|
destroyEmbedded()
|
|
{
|
|
accessEmbedded().~IFA();
|
|
}
|
|
|
|
|
|
template<class IMP>
|
|
PolymorphicValue (IMP*)
|
|
{
|
|
BOOST_STATIC_ASSERT (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 // mix-in, might be empty
|
|
{
|
|
virtual void
|
|
cloneInto (void* targetBuffer) const
|
|
{
|
|
new(targetBuffer) Adapter(*this); // forward to copy ctor
|
|
}
|
|
|
|
virtual void
|
|
copyInto (IFA& targetBase) const
|
|
{
|
|
REQUIRE (INSTANCEOF (Adapter, &targetBase));
|
|
Adapter& target = static_cast<Adapter&> (targetBase);
|
|
_AssignmentPolicy::assignEmbedded(target,*this);
|
|
} // forward to assignment operator
|
|
|
|
public: /* == forwarding ctor to implementation type == */
|
|
|
|
Adapter() : IMP() { }
|
|
|
|
template<typename A1>
|
|
Adapter (A1 a1) : IMP(a1) { }
|
|
|
|
template<typename A1, typename A2>
|
|
Adapter (A1 a1, A2 a2) : IMP(a1,a2) { }
|
|
|
|
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 = accessEmbedded();
|
|
return _Traits::accessCopyHandlingInterface (bufferContents);
|
|
}
|
|
|
|
|
|
public: /* === PolymorphicValue public API === */
|
|
|
|
typedef IFA Interface;
|
|
|
|
operator Interface& ()
|
|
{
|
|
return accessEmbedded();
|
|
}
|
|
operator Interface const& () const
|
|
{
|
|
return accessEmbedded();
|
|
}
|
|
Interface*
|
|
operator-> () const
|
|
{
|
|
return &( accessEmbedded() );
|
|
}
|
|
|
|
~PolymorphicValue()
|
|
{
|
|
destroyEmbedded();
|
|
}
|
|
|
|
PolymorphicValue (PolymorphicValue const& o)
|
|
{
|
|
o.accessHandlingInterface().cloneInto (&buf_);
|
|
}
|
|
|
|
PolymorphicValue&
|
|
operator= (PolymorphicValue const& o)
|
|
{
|
|
o.accessHandlingInterface().copyInto (this->accessEmbedded());
|
|
return *this;
|
|
}
|
|
|
|
/* === static factory functions === */
|
|
|
|
template<class IMP>
|
|
static PolymorphicValue
|
|
build ()
|
|
{
|
|
Adapter<IMP>* type_to_build_in_buffer(0);
|
|
return PolymorphicValue (type_to_build_in_buffer);
|
|
}
|
|
|
|
template<class IMP, typename A1>
|
|
static PolymorphicValue
|
|
build (A1 a1)
|
|
{
|
|
Adapter<IMP>* type_to_build_in_buffer(0);
|
|
return PolymorphicValue (type_to_build_in_buffer, a1);
|
|
}
|
|
|
|
template<class IMP, typename A1, typename A2>
|
|
static PolymorphicValue
|
|
build (A1 a1, A2 a2)
|
|
{
|
|
Adapter<IMP>* type_to_build_in_buffer(0);
|
|
return PolymorphicValue (type_to_build_in_buffer, a1,a2);
|
|
}
|
|
|
|
template<class IMP, typename A1, typename A2, typename A3>
|
|
static PolymorphicValue
|
|
build (A1 a1, A2 a2, A3 a3)
|
|
{
|
|
Adapter<IMP>* type_to_build_in_buffer(0);
|
|
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.accessEmbedded() == v2.accessEmbedded();
|
|
}
|
|
friend bool
|
|
operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2)
|
|
{
|
|
return ! (v1 == v2);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace lib
|
|
#endif
|