diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index 2fab78582..de2bcae7b 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -68,6 +68,8 @@ #include "lib/access-casted.hpp" #include "lib/util.hpp" +#include + namespace lib { @@ -540,7 +542,7 @@ namespace lib { return *this; } - // note: using standard copy operations + // note: using standard copy operations @@ -566,5 +568,169 @@ namespace lib { + + /** + * Variation of the concept realised by OpaqueHolder, but implemented here + * with reduced security and lesser overhead. InPlaceBuffer is just a chunk of + * storage, which can be accessed through a common base class interface and + * allows to place new objects there. It has no way to keep track of the + * actual object living currently in the buffer. Thus, using InPlaceBuffer + * requires the placed class(es) themselves to maintain their lifecycle, + * and especially it is mandatory for the base class to provide a + * virtual dtor. On the other hand, just the (alignment rounded) + * storage for the object(s) placed into the buffer is required. + */ + 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 DEFAULT = BA ///< the default instance to place initially + > + class InPlaceBuffer + : boost::noncopyable + { + + mutable char buf_[siz]; + + + BA& + getObj() const + { + return reinterpret_cast (buf_); + } + + void + placeDefault() + { + new(&buf_) DEFAULT(); + } + + void + destroy() + { + getObj().~BA(); + } + + + public: + InPlaceBuffer () + { + placeDefault(); + } + + ~InPlaceBuffer () + { + destroy(); + } + + + /** Abbreviation for placement new */ +#define LIB_InPlaceBuffer_CTOR(_CTOR_CALL_) \ + destroy(); \ + try \ + { \ + return *new(&buf_) _CTOR_CALL_; \ + } \ + catch (...) \ + { \ + placeDefault(); \ + throw; \ + } + + + template + TY& + create () + { + LIB_InPlaceBuffer_CTOR ( TY() ) + } + + + template + TY& //___________________________________________ + create (A1& a1) ///< place object of type TY, using 1-arg ctor + { + LIB_InPlaceBuffer_CTOR ( TY(a1) ) + } + + + template< class TY + , typename A1 + , typename A2 + > + TY& //___________________________________________ + create (A1& a1, A2& a2) ///< place object of type TY, using 2-arg ctor + { + LIB_InPlaceBuffer_CTOR ( TY(a1,a2) ) + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + > + TY& //___________________________________________ + create (A1& a1, A2& a2, A3& a3) ///< place object of type TY, using 3-arg ctor + { + LIB_InPlaceBuffer_CTOR ( TY(a1,a2,a3) ) + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + , typename A4 + > + TY& //___________________________________________ + create (A1& a1, A2& a2, A3& a3, A4& a4) ///< place object of type TY, using 4-arg ctor + { + LIB_InPlaceBuffer_CTOR ( TY(a1,a2,a3,a4) ) + } + + + template< class TY + , typename A1 + , typename A2 + , typename A3 + , typename A4 + , typename A5 + > + TY& //___________________________________________ + create (A1& a1, A2& a2, A3& a3, A4& a4, A5& a5) ///< place object of type TY, using 5-arg ctor + { + LIB_InPlaceBuffer_CTOR ( TY(a1,a2,a3,a4,a5) ) + } + + + + /* === smart-ptr style access === */ + + BA& + operator* () const + { + return getObj(); + } + + BA* + operator-> () const + { + return &getObj(); + } + + + template + static SUB* + access () + { + BA * asBase = &getObj(); + SUB* content = util::AccessCasted::access (asBase); + return content; + } // NOTE: might be null. + }; + + + + } // namespace lib #endif diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 1893dda10..595d945c7 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -43,6 +43,20 @@ namespace util { return (n==0)? 0 :((n<0)? -1:+1 ); } + template + inline N1 + min (N1 n1, N2 n2) + { + return n2 < n1? N1(n2) : n1; + } + + template + inline N1 + max (N1 n1, N2 n2) + { + return n1 < n2? N1(n2) : n1; + } + /** a family of util functions providing a "no value whatsoever" test. Works on strings and all STL containers, includes NULL test for pointers */ @@ -237,7 +251,7 @@ namespace util { - /** convienience shortcut: conversion to c-String via string. + /** convenience shortcut: conversion to c-String via string. * usable for printf with objects providing to-string conversion. */ inline const char* diff --git a/src/proc/control/command-argument-holder.hpp b/src/proc/control/command-argument-holder.hpp index 11480695c..d3c37860e 100644 --- a/src/proc/control/command-argument-holder.hpp +++ b/src/proc/control/command-argument-holder.hpp @@ -52,6 +52,7 @@ //#include //#include //#include +#include #include #include @@ -64,6 +65,7 @@ namespace control { // using boost::scoped_ptr; // using std::tr1::function; // using std::ostream; + using boost::noncopyable; using std::string; namespace { // empty state marker objects for ArgumentHolder @@ -115,7 +117,8 @@ namespace control { : public ArgumentTupleAccept< SIG // to derive the desired bind(..) signature , ArgumentHolder // target class providing the implementation , CmdClosure // base class to inherit from - > + > + , noncopyable { Closure arguments_; MementoTie memento_; diff --git a/tests/40components.tests b/tests/40components.tests index 59e355116..2dc20a375 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -281,6 +281,15 @@ return: 0 END +TEST "inline unchecked buffer" OpaqueUncheckedBuffer_test <: +out: DD<5>: \*\*\*\*\* +out: DD<9>: I'm fine\* +out: I'm special, what the f\*\* is going on here\?\*\*\*\*\*\*\*\*\*\*\*\* +return: 0 +END + + TEST "Lumiera Iterator Concept" IterAdapter_test 20 < @@ -27,70 +27,92 @@ #include "lib/util.hpp" #include "lib/opaque-holder.hpp" -#include "lib/bool-checkable.hpp" +#include #include -#include - +#include namespace lib { namespace test{ using ::Test; - using util::isnil; - using util::for_each; - using util::isSameObject; - using lumiera::error::LUMIERA_ERROR_INVALID; - using lumiera::error::LUMIERA_ERROR_ASSERTION; + using util::min; + using lumiera::error::LUMIERA_ERROR_FATAL; - using std::vector; + using boost::noncopyable; + using std::strlen; using std::cout; using std::endl; + + namespace { // test dummy hierarchy - // Note: common storage but no vtable + // Note: vtable (and virtual dtor), but varying storage requirements long _checksum = 0; uint _create_count = 0; - struct Base + struct Base + : noncopyable { uint id_; + + Base(uint i) : id_(i) { ++_create_count; _checksum += id_; } - Base(uint i=0) : id_(i) { _checksum +=id_; ++_create_count; } - Base(Base const& o) : id_(o.id_) { _checksum +=id_; ++_create_count; } + virtual ~Base() { } - uint getIt() { return id_; } + virtual void confess() =0; }; template struct DD : Base { - DD() : Base(ii) { } - ~DD() { _checksum -= ii; } // doing the decrement here - }; // verifies the correct dtor is called - - - struct Special - : DD<7> - , BoolCheckable - { - ulong myVal_; + char buff_[ii+1]; + + ~DD() { _checksum -= ii; } // verify the correct dtor is called... + + DD(Symbol sym = 0) + : Base(ii) + { + memset (&buff_, '*', ii); + if (sym) + memcpy (&buff_, sym, min (ii, strlen (sym))); + buff_[ii] = 0; + } - Special (uint val) - : myVal_(val) + void + confess () + { + cout << "DD<" << ii << ">: " << buff_ << endl; + } + }; + + + struct D42Sub + : DD<42> + { + D42Sub (string s1, string s2) + : DD<42> ((s1+' '+s2).c_str()) { } - bool - isValid () const ///< custom boolean "validity" check + void + confess () { - return myVal_ % 2; + cout << "I'm special, " << buff_ << endl; } }; + struct Killer + : DD<23> + { + Killer () { throw lumiera::error::Fatal ("crisscross"); } + }; + + + /** maximum additional storage maybe wasted * due to alignment of the contained object * within OpaqueHolder's buffer @@ -99,22 +121,14 @@ namespace test{ } - typedef OpaqueHolder Opaque; - typedef vector TestList; - /********************************************************************************** - * @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 \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) + * @test use an inline buffer to place objects of a subclass, without any checks. + * InPlaceBuffer only provides minimal service, to be covered here, + * including automatic dtor invocation and smart-ptr style access. */ - class OpaqueHolder_test : public Test + class OpaqueUncheckedBuffer_test : public Test { virtual void @@ -123,179 +137,36 @@ namespace test{ _checksum = 0; _create_count = 0; { - TestList objs = createDummies (); - for_each (objs, reAccess); - checkHandling (objs); - checkSpecialSubclass (); + typedef InPlaceBuffer), DD<0> > Buffer; + + Buffer buff; + ASSERT (sizeof(buff) <= sizeof(DD<42>) + _ALIGN_); + ASSERT (1 == _create_count); + ASSERT (0 == _checksum); + buff->confess(); // one default object of type DD<0> has been created + + buff.create > (); + buff->confess(); + + buff.create > ("I'm fine"); + buff->confess(); + + VERIFY_ERROR( FATAL, buff.create () ); + + ASSERT(0 == buff->id_); // default object was created, due to exception... + + buff.create ("what the f**","is going on here?"); + buff->confess(); + + ASSERT (6 == _create_count); + ASSERT (42 == _checksum); // No.42 is alive } - ASSERT (0 == _checksum); // all dead - } - - - TestList - createDummies () - { - TestList list; - list.push_back (DD<1>()); - list.push_back (DD<3>()); - list.push_back (DD<5>()); - list.push_back (DD<7>()); - return list; - } //note: copy - - - static void - reAccess (Opaque& elm) - { - cout << elm->getIt() << endl; - } - - - /** @test cover the basic situations of object handling, - * especially copy operations and re-assignments - */ - void - checkHandling (TestList& objs) - { - Opaque oo; - ASSERT (!oo); - ASSERT (isnil(oo)); - - oo = objs[1]; - ASSERT (oo); - ASSERT (!isnil(oo)); - - typedef DD<3> D3; - typedef DD<5> D5; - D3 d3 (oo.get() ); - ASSERT (3 == oo->getIt()); // re-access through Base interface - ASSERT (!isSameObject (d3, *oo)); - VERIFY_ERROR (WRONG_TYPE, oo.get() ); - - // direct assignment of target into Buffer - oo = D5(); - ASSERT (oo); - ASSERT (5 == oo->getIt()); - VERIFY_ERROR (WRONG_TYPE, oo.get() ); - - // 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()); - - // 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(); - 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 (_create_count == cnt_before); - oo = oo.get(); - ASSERT (_create_count == cnt_before); - oo = *oo; - ASSERT (_create_count == cnt_before); - ASSERT (oo); - - 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); - - Opaque o2 (d3); - ASSERT (!isSameObject (d3, *o2)); - ASSERT (3 == o2->getIt()); - - ASSERT (sizeof(Opaque) <= sizeof(Base) + sizeof(void*) + _ALIGN_); - } - - - /** @test OpaqueHolder with additional storage for subclass. - * When a subclass requires more storage than the base class or - * Interface, we need to create a custom OpaqueHolder, specifying the - * actually necessary storage. Such a custom OpaqueHolder behaves exactly - * like the standard variant, but there is protection against accidentally - * using a standard variant to hold an instance of the larger subclass. - * - * @test Moreover, if the concrete class has a custom operator bool(), it - * will be invoked automatically from OpaqueHolder's operator bool() - * - */ - void - checkSpecialSubclass () - { - typedef OpaqueHolder SpecialOpaque; - - cout << showSizeof() << endl; - cout << showSizeof() << endl; - cout << showSizeof() << endl; - cout << showSizeof() << endl; - - ASSERT (sizeof(Special) > sizeof(Base)); - ASSERT (sizeof(SpecialOpaque) > sizeof(Opaque)); - ASSERT (sizeof(SpecialOpaque) <= sizeof(Special) + sizeof(void*) + _ALIGN_); - - Special s1 (6); - Special s2 (3); - ASSERT (!s1); // even value - ASSERT (s2); // odd value - ASSERT (7 == s1.getIt()); // indeed subclass of DD<7> - ASSERT (7 == s2.getIt()); - - SpecialOpaque ospe0; - SpecialOpaque ospe1 (s1); - SpecialOpaque ospe2 (s2); - - ASSERT (!ospe0); // note: bool test (isValid) - ASSERT (!ospe1); // also forwarded to contained object (myVal_==6 is even) - ASSERT ( ospe2); - ASSERT ( isnil(ospe0)); // while isnil just checks the empty state - ASSERT (!isnil(ospe1)); - ASSERT (!isnil(ospe2)); - - ASSERT (7 == ospe1->getIt()); - ASSERT (6 == ospe1.get().myVal_); - ASSERT (3 == ospe2.get().myVal_); - - ospe1 = DD<5>(); // but can be reassigned like any normal Opaque - ASSERT (ospe1); - ASSERT (5 == ospe1->getIt()); - VERIFY_ERROR (WRONG_TYPE, ospe1.get() ); - - Opaque normal = DD<5>(); - ASSERT (normal); - ASSERT (5 == normal->getIt()); -#if false ////////////////////////////////////////////////////////TODO: restore throwing ASSERT - // Assertion protects against SEGV - VERIFY_ERROR (ASSERTION, normal = s1 ); -#endif//////////////////////////////////////////////////////////// + ASSERT (0 == _checksum); // all dead } }; - LAUNCHER (OpaqueHolder_test, "unit common"); + LAUNCHER (OpaqueUncheckedBuffer_test, "unit common"); }} // namespace lib::test