diff --git a/src/lib/access-casted.hpp b/src/lib/access-casted.hpp index 1a5d69f72..503262294 100644 --- a/src/lib/access-casted.hpp +++ b/src/lib/access-casted.hpp @@ -142,21 +142,24 @@ namespace util { using NullAccessor::access; template - static typename enable_if< use_dynamic_downcast, TAR>::type + static typename enable_if< use_dynamic_downcast, + TAR >::type access (ELM& elem) { return dynamic_cast (elem); } template - static typename enable_if< use_static_downcast, TAR>::type + static typename enable_if< use_static_downcast, + TAR >::type access (ELM& elem) { return static_cast (elem); } template - static typename enable_if< use_conversion, TAR>::type + static typename enable_if< use_conversion, + TAR >::type access (ELM& elem) { return elem; diff --git a/src/lib/meta/function-erasure.hpp b/src/lib/meta/function-erasure.hpp index 5690c745a..75bf5d2ce 100644 --- a/src/lib/meta/function-erasure.hpp +++ b/src/lib/meta/function-erasure.hpp @@ -98,10 +98,9 @@ namespace typelist{ /* ====== Policy classes ====== */ typedef function FunVoid; - typedef lib::OpaqueHolder< FunVoid - , sizeof(FunVoid) // same size for all function objects - , lib::OpaqueHolder_useBruteForceCast // no common base class! - > FunHolder; + typedef lib::InPlaceAnyHolder< sizeof(FunVoid) // same size for all function objects + , lib::InPlaceAnyHolder_unrelatedTypes // no common base class! + > FunHolder; /** * Policy for FunErasure: store an embedded tr1::function diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index 67e4eb6fc..e54116a30 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -96,15 +96,41 @@ namespace lib { * the actual Buff-subclasses will define overrides * with covariant return types. */ - template - class OpaqueHolder_useCommonBase - : public STO + template + struct InPlaceAnyHolder_useCommonBase { - typedef typename STO::BaseType Base; + typedef BA Base; - public: - /** @note a virtual get() function to be overridden */ - virtual Base& get() const =0; + template + static Base* + convert2base (SUB& obj) + { + SUB* oPtr = &obj; + BA* asBase = util::AccessCasted::access (oPtr); + if (asBase) + return asBase; + + throw lumiera::error::Logic ("Unable to convert concrete object to Base interface" + , LUMIERA_ERROR_WRONG_TYPE + ); + } + + template + static SUB* + access (Base* asBase) + { + // Because we don't know anything about the involved types, + // we need to exclude a brute force static cast + // (which might slice or reinterpret or even cause SEGV) + if (!util::use_static_downcast::value) + { + SUB* content = util::AccessCasted::access (asBase); + return content; + // might be NULL + } + else + return 0; + } }; /** @@ -121,14 +147,32 @@ namespace lib { * the contents of the buffer this way through * a brute force reinterpret_cast. */ - template - class OpaqueHolder_useBruteForceCast - : public STO + struct InPlaceAnyHolder_unrelatedTypes { + typedef void Base; + template + static void* + convert2base (SUB& obj) + { + return static_cast (&obj); + } + + template + static SUB* + access (Base*) + { + return 0; + } }; - + +/////////////////////////////TODO +/////////////////////////////: separieren in einen InPlaceAnyHolder +/////////////////////////////: und als dessen Kind den OpaqueHolder +/////////////////////////////: AccessPolicy unter die konkrete Buff-Klasse einschieben +/////////////////////////////: EmptyBuffer als abgeleitet. + /** @@ -144,58 +188,71 @@ namespace lib { * - the caller cares for thread safety. No concurrent get calls while in mutation! */ template - < class BA ///< the nominal Base/Interface class for a family of types - , size_t siz = sizeof(BA) ///< maximum storage required for the targets to be held inline - , template class AccessPolicy = OpaqueHolder_useCommonBase - ///< how to access the contents via a common interface? + < size_t siz ///< maximum storage required for the targets to be held inline + , class AccessPolicy = InPlaceAnyHolder_unrelatedTypes + ///< how to access the contents via a common interface? > - class OpaqueHolder - : public BoolCheckable > + class InPlaceAnyHolder + : public BoolCheckable > { + typedef typename AccessPolicy::Base * BaseP; - /** Storage buffer holding the target object inline */ - struct Storage + /** Inner capsule managing the contained object (interface) */ + struct Buffer { char content_[siz]; void* ptr() { return &content_; } - typedef BA BaseType; + virtual ~Buffer() {} + virtual bool isValid() const =0; + virtual bool empty() const =0; + virtual BaseP getBase() const =0; - virtual ~Storage() {} + virtual void clone (void* targetStorage) const =0; }; - /** Inner capsule managing the contained object (interface) */ - struct Buffer - : AccessPolicy + template + struct AbstractBuff : Buffer { - BA& - get() const + TY& + get() const ///< core operation: target is contained within the inline buffer { - return *reinterpret_cast (unConst(this)->ptr()); + return *reinterpret_cast (unConst(this)->ptr()); + } + + BaseP + getBase() const + { + return AccessPolicy::convert2base (get()); + } + + }; + + struct EmptyBuff : Buffer + { + virtual bool isValid() const { return false; } + virtual bool empty() const { return true; } + + BaseP + getBase() const + { + throw lumiera::error::Invalid("accessing empty holder"); } virtual void clone (void* targetStorage) const { - new(targetStorage) Buffer(); + new(targetStorage) EmptyBuff(); } - virtual bool isValid() const { return false; } - virtual bool empty() const { return true; } }; /** concrete subclass managing a specific kind of contained object. * @note invariant: content_ always contains a valid SUB object */ template - struct Buff : Buffer + struct Buff : AbstractBuff { - SUB& - get() const ///< core operation: target is contained within the inline buffer - { - return *reinterpret_cast (unConst(this)->ptr()); - } - ~Buff() { get().~SUB(); @@ -254,7 +311,7 @@ namespace lib { /* === internal interface for managing the storage === */ - + protected: Buffer& buff() { @@ -274,7 +331,7 @@ namespace lib { void make_emptyBuff() { - new(&storage_) Buffer(); + new(&storage_) EmptyBuff(); } template @@ -283,7 +340,7 @@ namespace lib { new(&storage_) Buff (obj); } - void clone_inBuff (OpaqueHolder const& ref) + void clone_inBuff (InPlaceAnyHolder const& ref) { ref.buff().clone (storage_); } @@ -291,7 +348,7 @@ namespace lib { public: - ~OpaqueHolder() + ~InPlaceAnyHolder() { killBuffer(); } @@ -304,24 +361,24 @@ namespace lib { } - OpaqueHolder() + InPlaceAnyHolder() { make_emptyBuff(); } template - OpaqueHolder(SUB const& obj) + InPlaceAnyHolder(SUB const& obj) { place_inBuff (obj); } - OpaqueHolder (OpaqueHolder const& ref) + InPlaceAnyHolder (InPlaceAnyHolder const& ref) { clone_inBuff (ref); } - OpaqueHolder& - operator= (OpaqueHolder const& ref) + InPlaceAnyHolder& + operator= (InPlaceAnyHolder const& ref) { if (!isSameObject (*this, ref)) { @@ -340,11 +397,11 @@ namespace lib { } template - OpaqueHolder& + InPlaceAnyHolder& operator= (SUB const& newContent) { - if ( !empty() - && !isSameObject (buff().get(), newContent) + if ( empty() + || !isSameObject (*buff().getBase(), newContent) ) { killBuffer(); @@ -362,21 +419,8 @@ namespace lib { } - /* === smart-ptr style access === */ - BA& - operator* () const - { - ASSERT (!empty()); - return buff().get(); - } - - BA* - operator-> () const - { - ASSERT (!empty()); - return &(buff().get()); - } + /* === re-accessing the concrete contained object === */ template SUB& get() const @@ -388,17 +432,13 @@ namespace lib { if (actual) return actual->get(); - // second try: maybe we can perform a - // dynamic downcast or direct conversion to the - // actual target type. But we need to exclude a - // brute force static cast (which might slice or reinterpret) - if (!util::use_static_downcast::value) - { - BA* asBase = &(buff().get()); - SUB* content = util::AccessCasted::access (asBase); - if (content) - return *content; - } + // second try: maybe we can perform a dynamic downcast + // or direct conversion to the actual target type. + BaseP asBase = buff().getBase(); + ASSERT (asBase); + SUB* content = AccessPolicy::template access (asBase); + if (content) + return *content; throw lumiera::error::Logic ("Attempt to access OpaqueHolder's contents " "specifying incompatible target type" @@ -423,6 +463,79 @@ 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. + * + * \par using OpaqueHolder + * OpaqueHolder instances are copyable value objects. They are created + * either empty, by copy from an existing OpaqueHolder, or by directly + * specifying the concrete object to embed. This target object will be + * \em copy-constructed into the internal buffer. Additionally, you + * may assign a new value, which causes the old value object to be + * destroyed and a new one to be copy-constructed into the buffer. + * Later on, the embedded value might be accessed + * - using the smart-ptr-like access through the common base interface BA + * - when knowing the exact type to access, the templated #get might be an option + * - the empty state of the container and a \c isValid() on the target may be checked + * - a combination of both is available as a \c bool check on the OpaqueHolder instance. + * + * 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! + */ + template + < class BA ///< the nominal Base/Interface class for a family of types + , size_t siz = sizeof(BA) ///< maximum storage required for the targets to be held inline + > + class OpaqueHolder + : public InPlaceAnyHolder > + { + typedef InPlaceAnyHolder > InPlaceHolder; + + public: + OpaqueHolder() : InPlaceHolder() {} + + template + OpaqueHolder(SUB const& obj) : InPlaceHolder(obj) {} + + template + OpaqueHolder& + operator= (SUB const& newContent) + { + static_cast(*this) = newContent; + return *this; + } + + // note: using standard copy operations + + + + /* === smart-ptr style access === */ + + BA& + operator* () const + { + ASSERT (!InPlaceHolder::empty()); + return *InPlaceHolder::buff().getBase(); + } + + BA* + operator-> () const + { + ASSERT (!InPlaceHolder::empty()); + return InPlaceHolder::buff().getBase(); + } + + }; + + + + } // namespace lib #endif diff --git a/tests/lib/opaque-holder-test.cpp b/tests/lib/opaque-holder-test.cpp index e026e1ffd..26b9fb25d 100644 --- a/tests/lib/opaque-holder-test.cpp +++ b/tests/lib/opaque-holder-test.cpp @@ -50,14 +50,15 @@ namespace test{ // Note: common storage but no vtable long _checksum = 0; + uint _create_count = 0; struct Base { uint id_; - Base(uint i=0) : id_(i) { _checksum +=id_; } - Base(Base const& o) : id_(o.id_) { _checksum +=id_; } + Base(uint i=0) : id_(i) { _checksum +=id_; ++_create_count; } + Base(Base const& o) : id_(o.id_) { _checksum +=id_; ++_create_count; } uint getIt() { return id_; } }; @@ -119,6 +120,7 @@ namespace test{ run (Arg) { _checksum = 0; + _create_count = 0; { TestList objs = createDummies (); for_each (objs, reAccess); @@ -171,18 +173,34 @@ namespace test{ ASSERT (5 == oo->getIt()); VERIFY_ERROR (WRONG_TYPE, oo.get() ); - D5 &rd5 (oo.get()); + D5 &rd5 (oo.get()); // can get a direct reference to contained object 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! + ASSERT (isSameObject (rd5, *oo)); + + uint cnt_before = _create_count; + + oo.clear(); + ASSERT (!oo); + 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() + // verify that self-assignment is properly detected... + cnt_before = _create_count; oo = oo; ASSERT (oo); - ASSERT (isSameObject (rd5, *oo)); + ASSERT (_create_count == cnt_before); oo = oo.get(); - ASSERT (isSameObject (rd5, *oo)); - ASSERT (oo); + ASSERT (_create_count == cnt_before); oo = *oo; - ASSERT (isSameObject (rd5, *oo)); + ASSERT (_create_count == cnt_before); ASSERT (oo); oo.clear();