diff --git a/src/lib/meta/function-erasure.hpp b/src/lib/meta/function-erasure.hpp index 75bf5d2ce..eaca137d0 100644 --- a/src/lib/meta/function-erasure.hpp +++ b/src/lib/meta/function-erasure.hpp @@ -101,7 +101,11 @@ namespace typelist{ typedef lib::InPlaceAnyHolder< sizeof(FunVoid) // same size for all function objects , lib::InPlaceAnyHolder_unrelatedTypes // no common base class! > FunHolder; + typedef lib::InPlaceAnyHolder< sizeof(void*) + , lib::InPlaceAnyHolder_unrelatedTypes + > FunPtrHolder; + /** * Policy for FunErasure: store an embedded tr1::function * Using this policy allows to store arbitrary complex functor objects @@ -111,7 +115,6 @@ namespace typelist{ class StoreFunction : public FunHolder { - public: template StoreFunction (SIG& fun) @@ -139,59 +142,26 @@ namespace typelist{ * The price to pay is vtable access. */ class StoreFunPtr - : public lib::BoolCheckable + : public FunPtrHolder { - /** Helper: type erasure */ - struct Holder - { - void *fP_; - virtual ~Holder() {} - }; - - /** storing and retrieving concrete function ptr */ - template - struct FunctionHolder : Holder - { - FunctionHolder (SIG *fun) - { - REQUIRE (fun); - fP_ = reinterpret_cast (fun); - } - SIG& - get() - { - return *reinterpret_cast (fP_); - } - }; - - /** embedded container holding the pointer */ - Holder holder_; - public: template StoreFunPtr (SIG& fun) - { - new(&holder_) FunctionHolder (&fun); - } + : FunPtrHolder(&fun) + { } + template StoreFunPtr (SIG *fun) - { - new(&holder_) FunctionHolder (fun); - } + : FunPtrHolder(fun) + { } template SIG& getFun () { - REQUIRE (INSTANCEOF (FunctionHolder, &holder_)); - return static_cast&> (holder_).get(); - } - - - bool - isValid() const - { - return holder_.fP_; + SIG *fun = get(); + REQUIRE (fun); + return *fun; } }; diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index e54116a30..d3a9b38fc 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -29,11 +29,21 @@ ** an inline buffer holding an object of the concrete subclass. Typically, ** this situation arises when dealing with functor objects. ** - ** This template helps building custom objects and wrappers based on this - ** pattern: it provides an buffer for the target objects and controls access - ** through a two-layer capsule; while the outer container exposes a neutral - ** interface, the inner container keeps track of the actual type by means - ** of a vtable. OpaqueHolder can be empty; but re-accessing the concrete + ** These templates help with building custom objects and wrappers based on + ** this pattern: InPlaceAnyHolder provides an buffer for the target objects + ** and controls access through a two-layer capsule; while the outer container + ** exposes a neutral interface, the inner container keeps track of the actual + ** type by means of a vtable. OpaqueHolder is built on top of InPlaceAnyHolder + ** additionally to support a "common base interface" and re-access of the + ** embedded object through this interface. For this to work, all of the + ** stored types need to be derived from this common base interface. + ** OpaqueHolder then may be even used like a smart-ptr, exposing this + ** base interface. To the contrary, InPlaceAnyHolder has lesser requirements + ** on the types to be stored within. It can be configured with policy classes + ** to control the re-access; when using InPlaceAnyHolder_unrelatedTypes + ** the individual types to be stored need not be related in any way, but + ** of course this rules out anything beyond re-accessing the embedded object + ** by knowing it's exact type. Generally speaking, re-accessing the concrete ** object requires knowledge of the actual type, similar to boost::any ** (but contrary to OpaqueHolder the latter uses heap storage). ** @@ -87,14 +97,15 @@ namespace lib { } - /* ==== Policy classes for OpaqueHolder ==== */ + + + /* ==== Policy classes controlling re-Access ==== */ /** * Standard policy for accessing the contents via * a common base class interface. Using this policy - * causes a virtual get() function to be used, where - * the actual Buff-subclasses will define overrides - * with covariant return types. + * causes static or dynamic casts or direct conversion + * to be employed as appropriate. */ template struct InPlaceAnyHolder_useCommonBase @@ -134,18 +145,15 @@ namespace lib { }; /** - * Alternative policy for accessing the contents via + * Alternative policy for accessing the contents without * a common interface; use this policy if the intention is * to use OpaqueHolder with a family of similar classes, * \em without requiring all of them to be derived from * a common base class. (E.g. tr1::function objects). - * In this case, the BA template parameter of OpaqueHolder - * can be considered just a nominal placeholder. - * @note using this policy renders the smart-ptr style - * access largely useless and might even cause - * segmentation errors, when trying to access - * the contents of the buffer this way through - * a brute force reinterpret_cast. + * In this case, the "Base" type will be defined to void* + * As a consequence, we loose all type information and + * no conversions are possible on re-access. You need + * to know the \em exact type to get back at the object. */ struct InPlaceAnyHolder_unrelatedTypes { @@ -166,26 +174,28 @@ namespace lib { } }; - -/////////////////////////////TODO -/////////////////////////////: separieren in einen InPlaceAnyHolder -/////////////////////////////: und als dessen Kind den OpaqueHolder -/////////////////////////////: AccessPolicy unter die konkrete Buff-Klasse einschieben -/////////////////////////////: EmptyBuffer als abgeleitet. - + + + + /** * 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. + * concrete type. The object is given either as ctor parameter or + * by direct assignment; it is copy-constructed into the buffer. + * It is necessary to specify the required buffer storage space + * as a template parameter. InPlaceAnyHolder may be created empty + * or cleared afterwards, and this #empty() state may be detected + * at runtime. In a similar vein, when the stored object has a + * \c bool validity check, this can be accessed though #isValid(). + * Moreover \code !empty() && isValid() \endcode may be tested + * as by \bool conversion of the Holder object. The whole compound + * is copyable if and only if the contained object is copyable. * - * 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! + * @note assertion failure when trying to place an instance not + * fitting into given size. + * @note \em not threadsafe! */ template < size_t siz ///< maximum storage required for the targets to be held inline @@ -204,30 +214,14 @@ namespace lib { void* ptr() { return &content_; } virtual ~Buffer() {} - virtual bool isValid() const =0; - virtual bool empty() const =0; - virtual BaseP getBase() const =0; - - virtual void clone (void* targetStorage) const =0; - }; - - template - struct AbstractBuff : Buffer - { - TY& - get() const ///< core operation: target is contained within the inline buffer - { - return *reinterpret_cast (unConst(this)->ptr()); - } - - BaseP - getBase() const - { - return AccessPolicy::convert2base (get()); - } + virtual bool isValid() const =0; + virtual bool empty() const =0; + virtual BaseP getBase() const =0; + virtual void clone (void* targetStorage) const =0; }; + /** special case: no stored object */ struct EmptyBuff : Buffer { virtual bool isValid() const { return false; } @@ -244,15 +238,20 @@ namespace lib { { new(targetStorage) EmptyBuff(); } - }; /** concrete subclass managing a specific kind of contained object. - * @note invariant: content_ always contains a valid SUB object */ + * @note invariant: #content_ always contains a valid SUB object */ template - struct Buff : AbstractBuff + struct Buff : Buffer { + SUB& + get() const ///< core operation: target is contained within the inline buffer + { + return *reinterpret_cast (unConst(this)->ptr()); + } + ~Buff() { get().~SUB(); @@ -269,20 +268,27 @@ namespace lib { { new(Buffer::ptr()) SUB (oBuff.get()); } - + Buff& - operator= (Buff const& ref) ///< not used currently + operator= (Buff const& ref) ///< currently not used { if (&ref != this) get() = ref.get(); return *this; } + /* == virtual access functions == */ virtual void clone (void* targetStorage) const { - new(targetStorage) Buff(this->get()); + new(targetStorage) Buff(get()); + } + + virtual BaseP + getBase() const + { + return AccessPolicy::convert2base (get()); } virtual bool @@ -299,6 +305,7 @@ namespace lib { }; + enum{ BUFFSIZE = sizeof(Buffer) }; /** embedded buffer actually holding the concrete Buff object, @@ -310,8 +317,9 @@ namespace lib { - /* === internal interface for managing the storage === */ - protected: + + protected: /* === internal interface for managing the storage === */ + Buffer& buff() { @@ -324,23 +332,27 @@ namespace lib { } - void killBuffer() - { + void + killBuffer() + { buff().~Buffer(); } - void make_emptyBuff() + void + make_emptyBuff() { new(&storage_) EmptyBuff(); } template - void place_inBuff (SUB const& obj) + void + place_inBuff (SUB const& obj) { new(&storage_) Buff (obj); } - void clone_inBuff (InPlaceAnyHolder const& ref) + void + clone_inBuff (InPlaceAnyHolder const& ref) { ref.buff().clone (storage_); } @@ -348,6 +360,7 @@ namespace lib { public: + ~InPlaceAnyHolder() { killBuffer(); @@ -420,8 +433,21 @@ namespace lib { - /* === re-accessing the concrete contained object === */ - + /** re-accessing the concrete contained object. + * When the specified type is exactly the same + * as used when storing the object, we can directly + * re-access the buffer. Otherwise, a conversion might + * be possible going through the Base type, depending + * on the actual types involved and the AccessPolicy. + * But, as we don't "know" the actual type of the object + * in storage, a \em static upcast to any type \em between + * the concrete object type and the base type has to be + * ruled out for safety reasons. When the contained object + * has RTTI, a \em dynamic cast can be performed in this + * situation. You might consider using visitor.hpp instead + * if this imposes a serious limitation. + * @throws lumiera::error::Logic when conversion/access fails + */ template SUB& get() const { @@ -463,13 +489,16 @@ 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. + * 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 diff --git a/tests/lib/meta/function-erasure-test.cpp b/tests/lib/meta/function-erasure-test.cpp index aadd03e14..4c0922e2e 100644 --- a/tests/lib/meta/function-erasure-test.cpp +++ b/tests/lib/meta/function-erasure-test.cpp @@ -211,15 +211,16 @@ namespace test { // fabricate an unbound functor... typedef typename BuildEmptyFunctor::Type NoFunc; - NoFunc noFunction; + NoFunc noFunction = NoFunc(); HOL emptyHolder (noFunction); ASSERT (!emptyHolder); + ASSERT ( h1 ); ASSERT ( h2 ); ASSERT ( h3 ); } - + }; diff --git a/tests/lib/opaque-holder-test.cpp b/tests/lib/opaque-holder-test.cpp index 26b9fb25d..90d9a5333 100644 --- a/tests/lib/opaque-holder-test.cpp +++ b/tests/lib/opaque-holder-test.cpp @@ -40,6 +40,7 @@ namespace test{ using util::isnil; using util::for_each; using util::isSameObject; + using lumiera::error::LUMIERA_ERROR_INVALID; using lumiera::error::LUMIERA_ERROR_ASSERTION; using std::vector; @@ -88,13 +89,13 @@ namespace test{ return myVal_ % 2; } }; - - - /** maximum additional storage maybe wasted - * due to alignment of the contained object - * within OpaqueHolder's buffer - */ - const size_t ALLIGNMENT = sizeof(size_t); + + + /** maximum additional storage maybe wasted + * due to alignment of the contained object + * within OpaqueHolder's buffer + */ + const size_t ALLIGNMENT = sizeof(size_t); } @@ -104,14 +105,14 @@ namespace test{ /********************************************************************************** - * @test use the OpaqueHolder inline buffer to handle a family of classes + * @test use the OpaqueHolder inline buffer to handle objects of a family of types * through a common interface, without being forced to use heap storage * or a custom allocator. - * - * @todo this test doesn't cover automatic conversions and conversions using - * RTTI from the target objects, while OpaqueHolder.tempate get() would - * allow for such conversions. This is similar to Ticket #141, and actually - * based on the same code as variant.hpp (access-casted.hpp) + * + * @todo this test doesn't cover automatic conversions and conversions using RTTI + * from the target objects, while \code OpaqueHolder.template get() \endcode + * would allow for such conversions. This is similar to Ticket #141, and + * actually based on the same code as variant.hpp (access-casted.hpp) */ class OpaqueHolder_test : public Test { @@ -150,6 +151,9 @@ namespace test{ } + /** @test cover the basic situations of object handling, + * especially copy operations and re-assignments + */ void checkHandling (TestList& objs) { @@ -164,25 +168,28 @@ namespace test{ typedef DD<3> D3; typedef DD<5> D5; D3 d3 (oo.get() ); - ASSERT (3 == oo->getIt()); + ASSERT (3 == oo->getIt()); // re-access through Base interface ASSERT (!isSameObject (d3, *oo)); VERIFY_ERROR (WRONG_TYPE, oo.get() ); - oo = D5(); // direct assignment of target into Buffer + // direct assignment of target into Buffer + oo = D5(); ASSERT (oo); ASSERT (5 == oo->getIt()); VERIFY_ERROR (WRONG_TYPE, oo.get() ); - D5 &rd5 (oo.get()); // can get a direct reference to contained object + // can get a direct reference to contained object + D5 &rd5 (oo.get()); 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! + // WARNING: direct ref has been messed up through the backdoor! + ASSERT (7 == rd5.getIt()); ASSERT (isSameObject (rd5, *oo)); - + uint cnt_before = _create_count; oo.clear(); @@ -190,7 +197,9 @@ namespace test{ 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() + 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; @@ -206,6 +215,11 @@ namespace test{ oo.clear(); ASSERT (!oo); ASSERT (isnil(oo)); + VERIFY_ERROR (INVALID, oo.get() ); +#if false ////////////////////////////////////////////////////////TODO: restore throwing ASSERT + VERIFY_ERROR (ASSERTION, oo->getIt() ); +#endif//////////////////////////////////////////////////////////// + // can't access empty holder... Opaque o1 (oo); ASSERT (!o1);