diff --git a/src/lib/polymorphic-value.hpp b/src/lib/polymorphic-value.hpp new file mode 100644 index 000000000..bd343342f --- /dev/null +++ b/src/lib/polymorphic-value.hpp @@ -0,0 +1,287 @@ +/* + POLYMORPHIC-VALUE.hpp - building opaque polymorphic value objects + + Copyright (C) Lumiera.org + 2011, Hermann Vosseler + + 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 frequently encountered in + ** C++ programming, based on the fundamental design of C++, favouring + ** 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++ to employ many of the well known techniques, you're more or less + ** bound to explicitly put the objects somewhere in hash allocated memory + ** and then pass an interface pointer or reference into the actual algorithm. + ** Often, 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). Thus, the ability to return a polymorphic object from a + ** factory or configuration function by value 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 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 type and layout + ** + ** @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/bool-checkable.hpp" +//#include "lib/access-casted.hpp" +//#include "lib/util.hpp" + +//#include + + +namespace lib { + +// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +// using util::isSameObject; +// using util::unConst; + + + namespace { // implementation helpers... + + + } + + + + + + /** + * 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 further. + */ + template + < class IFA ///< the nominal Base/Interface class for a family of types + , size_t siz ///< maximum storage required for the targets to be held inline + > + class PolymorphicValue + { + mutable char buf_[siz]; + + template + IMP& + access() const + { + return reinterpret_cast (buf_); + } + + void + destroy() + { + access().~IFA(); + } + +// REQUIRE (siz >= sizeof(IMP)); + + template + PolymorphicValue (IMP*) + { + new(&buf_) IMP(); + } + + template + PolymorphicValue (IMP*, A1& a1) + { + new(&buf_) IMP (a1); + } + + template + PolymorphicValue (IMP*, A1& a1, A2& a2) + { + new(&buf_) IMP (a1,a2); + } + + template + PolymorphicValue (IMP*, A1& a1, A2& a2, A3& a3) + { + new(&buf_) IMP (a1,a2,a3); + } + + + class HandlingAdapter + { + public: + virtual ~HandlingAdapter() { }; + virtual void cloneInto (void* targetBuffer) const =0; + virtual void copyInto (IFA& targetBase) const =0; + }; + + template + class Adapter + : public IMP + , public HandlingAdapter + { + virtual void + cloneInto (void* targetBuffer) const + { + new(targetBuffer) Adapter(*this); + } + + virtual void + copyInto (IFA& targetBase) const + { + REQUIRE (INSTANCEOF (Adapter, &targetBase)); + Adapter& target = static_cast (targetBase); + target = (*this); + } + + public: + /* using default copy and assignment */ + Adapter() : IMP() { } + + template + Adapter (A1& a1) : IMP(a1) { } + + template + Adapter (A1& a1, A2& a2) : IMP(a1,a2) { } + + template + Adapter (A1& a1, A2& a2, A3& a3) : IMP(a1,a2,a3) { } + }; + + + HandlingAdapter& + accessHandlingInterface () const + { + IFA& bufferContents = access(); + REQUIRE (INSTANCEOF (HandlingAdapter, &bufferContents)); + HandlingAdapter& hap = dynamic_cast (bufferContents); + return hap; + } + + public: + operator IFA& () + { + return access(); + } + operator IFA const& () const + { + return access(); + } + + ~PolymorphicValue() + { + destroy(); + } + + PolymorphicValue (PolymorphicValue const& o) + { + o.accessHandlingInterface().cloneInto (&buf_); + } + + PolymorphicValue& + operator= (PolymorphicValue const& o) + { + o.accessHandlingInterface().copyInto (this->access()); + return *this; + } + + template + static PolymorphicValue + build () + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer); + } + + template + static PolymorphicValue + build (A1& a1) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1); + } + + template + static PolymorphicValue + build (A1& a1, A2& a2) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1,a2); + } + + template + static PolymorphicValue + build (A1& a1, A2& a2, A3& a3) + { + Adapter* type_to_build_in_buffer; + return PolymorphicValue (type_to_build_in_buffer, a1,a2,a3); + } + + friend bool + operator== (PolymorphicValue const& v1, PolymorphicValue const& v2) + { + return v1.access() == v2.access(); + } + friend bool + operator!= (PolymorphicValue const& v1, PolymorphicValue const& v2) + { + return ! (v1 == v2); + } + }; + + + + +} // namespace lib +#endif diff --git a/tests/lib/polymorphic-value-test.cpp b/tests/lib/polymorphic-value-test.cpp index d56e2a122..755cbe3a9 100644 --- a/tests/lib/polymorphic-value-test.cpp +++ b/tests/lib/polymorphic-value-test.cpp @@ -27,7 +27,7 @@ #include "lib/util.hpp" #include "lib/util-foreach.hpp" -#include "lib/opaque-holder.hpp" +#include "lib/polymorphic-value.hpp" //#include "lib/bool-checkable.hpp" #include @@ -41,11 +41,12 @@ 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::vector; // using std::cout; // using std::endl; @@ -62,15 +63,16 @@ namespace test{ virtual long& localSum() =0; bool - operator== (Interface const& o) + operator== (Interface const& o) const { - return localSum() == o.localSum(); + return unConst(this)->localSum() + == unConst(o).localSum(); } }; const uint MAX_RAND = 1000; - const uint MAX_SIZ = sizeof(long[111]); + const uint MAX_SIZ = sizeof(long[113]); /////////////////////TODO: using just 111 causes SEGV ---> suspect the HandlingAdapter mixin to require additional storage long _checkSum = 0; long _callSum = 0; @@ -109,7 +111,7 @@ namespace test{ long& localSum() { - return localData[ii-1]; + return localData_[ii-1]; } void @@ -130,7 +132,7 @@ namespace test{ } typedef PolymorphicValue PolyVal; - typedef vector TestList; + typedef std::vector TestList; @@ -154,7 +156,7 @@ namespace test{ TestList objs = createOpaqueValues (); for_each (objs, operate); } - CHECK (0 == _checksum); // all dead + CHECK (0 == _checkSum); // all dead verifyOverrunProtection(); } @@ -180,7 +182,8 @@ namespace test{ CHECK (elm == myLocalVal); long prevSum = _callSum; - long randVal = myLocalVal.apiFunc(); + Interface& subject = myLocalVal; + long randVal = subject.apiFunc(); CHECK (prevSum + randVal == _callSum); CHECK (elm != myLocalVal);