From 6daf14211ba2168a0fedcae9d30f22c278148125 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 24 Apr 2011 20:28:36 +0200 Subject: [PATCH] Finish the PolymorphicValue support template --- src/lib/polymorphic-value.hpp | 172 ++++++++++++++++++++------- tests/lib/polymorphic-value-test.cpp | 75 ++++++++---- 2 files changed, 186 insertions(+), 61 deletions(-) diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp index 70a8bd35a..94a4b4b09 100644 --- a/src/lib/polymorphic-value.hpp +++ b/src/lib/polymorphic-value.hpp @@ -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 #include 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 + * (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 CopySupport : public BA @@ -144,7 +188,10 @@ namespace lib { - + /** + * helper to detect presence of a function + * to support clone operations + */ template 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 struct Trait { - typedef CopySupport CopyAPI; + typedef CopySupport CopyAPI; enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) }; static CopyAPI& accessCopyHandlingInterface (TY& bufferContents) { REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); - return dynamic_cast (bufferContents); + return dynamic_cast (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 struct Trait >::type> { @@ -185,13 +247,13 @@ namespace lib { accessCopyHandlingInterface (IFA& bufferContents) { REQUIRE (INSTANCEOF (CopyAPI, &bufferContents)); - return static_cast (bufferContents); + return static_cast (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 - IMP& - access() const + IFA& + accessEmbedded() const { - return reinterpret_cast (buf_); + return reinterpret_cast (buf_); } void - destroy() + destroyEmbedded() { - access().~IFA(); + accessEmbedded().~IFA(); } -// REQUIRE (siz >= sizeof(IMP)); template PolymorphicValue (IMP*) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP(); } template PolymorphicValue (IMP*, A1& a1) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP (a1); } template PolymorphicValue (IMP*, A1& a1, A2& a2) { + REQUIRE (siz >= sizeof(IMP)); new(&buf_) IMP (a1,a2); } template 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 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 (targetBase); - target = (*this); + target = (*this); // forward to assignment operator } - public: - /* using default copy and assignment */ + public: /* == forwarding ctor to implementation type == */ + Adapter() : IMP() { } template @@ -300,31 +378,40 @@ namespace lib { template Adapter (A1& a1, A2& a2, A3& a3) : IMP(a1,a2,a3) { } + + /* using default copy and assignment */ }; _CopyHandlingAdapter& accessHandlingInterface () const { - IFA& bufferContents = access(); - _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(); + return accessEmbedded(); } - operator IFA const& () const + operator Interface const& () const { - return access(); + 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()); + o.accessHandlingInterface().copyInto (this->accessEmbedded()); return *this; } + /* === static factory functions === */ + template 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() == v2.access(); + return v1.accessEmbedded() == v2.accessEmbedded(); } friend bool operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2) diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index 26570c8ff..bdc366f9c 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -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 -//#include #include @@ -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 - 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 + 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 MaximumSizedImp; + + // Standard case: no copy support by client objects + verifyCreation_and_Copy(); + + // Special case: client objects expose extension point for copy support + typedef polyvalue::CopySupport CopySupportAPI; // Copy support API declared as sub-interface + typedef Imp CopySupportingImp; // insert this sub-interface between public API and Implementation + typedef PolymorphicValue 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(); + } + + + template + 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 >(); + Holder val = Holder::template build(); 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 OversizedImp; + CHECK (MAX_SIZ < sizeof(OversizedImp)); #if false ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT - VERIFY_ERROR (ASSERTION, PolyVal::build >() ); + VERIFY_ERROR (ASSERTION, PolyVal::build() ); #endif ///////////////////////////////////////////////////////////////////////////////////////////////TICKET #537 : restore throwing ASSERT } };