diff --git a/src/lib/scopedholder.hpp b/src/lib/scopedholder.hpp index 8d1564731..0019b6966 100644 --- a/src/lib/scopedholder.hpp +++ b/src/lib/scopedholder.hpp @@ -39,7 +39,15 @@ ** ScopedHolder implements a similar concept for in-place storage of ** noncopyable objects within STL containers. ** + ** While the added copy operations (secured with the "empty" requirement) + ** are enough to use those holders within fixed sized STL containers, + ** supporting dynamic growth (like in std::vector#resize() ) additionally + ** requires a facility to transfer the lifecycle management control between + ** holder instances. This is the purpose of the \c transfer_control + ** friend function. + ** ** @see scopedholdertest.cpp + ** @see scopedholdertransfer.hpp use in std::vector ** @see AllocationCluster usage example */ @@ -69,6 +77,13 @@ namespace lib { { typedef boost::scoped_ptr _Parent; + static B* must_be_null (_Parent const& ptr) + { + if (ptr) + throw lumiera::error::Logic("ScopedPtrHolder protocol violation: " + "attempt to copy from non-null."); + return 0; + } public: ScopedPtrHolder () @@ -97,14 +112,13 @@ namespace lib { return *this; } - - private: - static B* must_be_null (_Parent const& ptr) + friend void + transfer_control (ScopedPtrHolder& from, ScopedPtrHolder& to) { - if (ptr) - throw lumiera::error::Logic("ScopedPtrHolder protocol violation: " - "attempt to copy from non-null."); - return 0; + if (!from) return; + TRACE (test, "transfer_control... from=%x to=%x",&from, &to); + must_be_null (to); + to.swap(from); } }; @@ -133,11 +147,22 @@ namespace lib { typedef ScopedHolder _ThisType; + static char must_be_empty (_ThisType const& ref) + { + if (ref) + throw lumiera::error::Logic("ScopedHolder protocol violation: " + "copy operation after having invoked create()."); + return 0; + } + public: ScopedHolder() : created_(0) { } + ~ScopedHolder() { clear(); } + + TY& create () { @@ -147,7 +172,8 @@ namespace lib { return *obj; } - ~ScopedHolder() + void + clear () { if (created_) get()->~TY(); @@ -198,16 +224,28 @@ namespace lib { bool operator! () const { return !created_; } - private: - static char must_be_empty (_ThisType const& ref) + friend void + transfer_control (ScopedHolder& from, ScopedHolder& to) { - if (ref) - throw lumiera::error::Logic("ScopedHolder protocol violation: " - "copy operation after having invoked create()."); - return 0; + if (!from) return; + TRACE (test, "transfer_control... from=%x to=%x",&from, &to); + must_be_empty (to); + to.create(); + try + { + transfer_control(*from,*to); // note: assumed to have no side-effect in case it throws + from.clear(); + return; + } + catch(...) + { + to.clear(); + WARN (test, "transfer_control operation aborted."); + throw; + } } }; - + diff --git a/src/lib/scopedholdertransfer.hpp b/src/lib/scopedholdertransfer.hpp new file mode 100644 index 000000000..d894de114 --- /dev/null +++ b/src/lib/scopedholdertransfer.hpp @@ -0,0 +1,137 @@ +/* + SCOPEDHOLDERVECTOR.hpp - using ScopedHolder within a STL vector + + Copyright (C) Lumiera.org + 2008, 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. + +*/ + + + +#ifndef LIB_SCOPEDHOLDERVECTOR_H +#define LIB_SCOPEDHOLDERVECTOR_H + +#include "common/error.hpp" +#include + + +namespace lib { + + + + + /** + * Addendum to scopedholder.hpp for transferring the lifecycle + * management to another instance. Using these wrappers within + * STL vector and similar containers may result in the need to + * do a re-allocation in response to a request to grow. + * Obviously such a feature needs support by the objects being + * wrapped, which should provide an operation for transferring + * lifecycle management in a controlled fashion. This behaviour + * is similar to std::auto_ptr, but because we use a separate + * dedicated operation, we avoid some of the dangers pertaining + * the use of the latter: just taking the "value" can't kill + * the managed object. + * \par + * To implement this feature we need + * - a custom allocator to be used by the vector. By default + * it is built as a thin proxy round std::allocator. + * - the \em noncopyable type to be managed within the vector + * needs to provide a custom extension point: when the + * allocator detects the need to transfer control between + * two instances, it will invoke a free function named + * transfer_control(TY& from, TY& to) intended + * to be found by ADL. Note: in case this function throws, + * it must not have any side effects. + * - besides, the \em noncopyable type needs to provide an + * operator bool() yielding true iff currently + * containing an managed object. This is similar to + * boost::scoped_ptr or even the behaviour of a plain + * old raw pointer, which is equivalent to \c true + * when the pointer isn'T \c NULL + * + */ + template > + class Allocator_TransferNoncopyable + { + typedef Allocator_TransferNoncopyable _ThisType; + + PAR par_; + + + public: + typedef typename PAR::size_type size_type; + typedef typename PAR::difference_type difference_type; + typedef typename PAR::pointer pointer; + typedef typename PAR::const_pointer const_pointer; + typedef typename PAR::reference reference; + typedef typename PAR::const_reference const_reference; + typedef typename PAR::value_type value_type; + + template + struct rebind + { typedef Allocator_TransferNoncopyable other; }; + + Allocator_TransferNoncopyable() { } + + Allocator_TransferNoncopyable(const _ThisType& allo) + : par_(allo.par_) { } + Allocator_TransferNoncopyable(const PAR& allo) + : par_(allo) { } + + template + Allocator_TransferNoncopyable(const std::allocator&) { } + + ~Allocator_TransferNoncopyable() { } + + + //------------proxying-the-parent-allocator------------------------------------ + + size_type max_size() const { return par_.max_size(); } + pointer address(reference r) const { return par_.address(r); } + const_pointer address(const_reference cr) const { return par_.address(cr); } + pointer allocate(size_type n, const void *p=0){ return par_.allocate(n,p); } + void deallocate(pointer p, size_type n) { return par_.deallocate(p,n); } + void destroy(pointer p) { return par_.destroy(p); } + + + void + construct (pointer p, const TY& ref) + { + new(p) TY(); + ASSERT (p); + ASSERT (!(*p), "protocol violation: target already manages another object."); + if (ref) + transfer_control (const_cast(ref), *p); + } + }; + + template + inline bool + operator== (Allocator_TransferNoncopyable const&, Allocator_TransferNoncopyable const&) + { return true; } + + template + inline bool + operator!= (Allocator_TransferNoncopyable const&, Allocator_TransferNoncopyable const&) + { return false; } + + + + +} // namespace lib +#endif diff --git a/src/tool/SConscript b/src/tool/SConscript index 9cbf5c8b5..2336c3553 100644 --- a/src/tool/SConscript +++ b/src/tool/SConscript @@ -9,6 +9,6 @@ Import('env','artifacts','core') # build the ubiquitous Hello World application (note: C source) artifacts['tools'] = [ env.Program('#$BINDIR/hello-world','hello.c') + env.Program('#$BINDIR/luidgen', ['luidgen.c']+core) - + env.Program('#$BINDIR/try', ['try.cpp']+core) #### to try out some feature... + + env.Program('#$BINDIR/try', 'try.cpp') #### to try out some feature... ] diff --git a/src/tool/try.cpp b/src/tool/try.cpp index 5741ccf6f..5d1034645 100644 --- a/src/tool/try.cpp +++ b/src/tool/try.cpp @@ -14,20 +14,16 @@ // 10/8 - abusing the STL containers to hold noncopyable values -//#include -#include "proc/nobugcfg.hpp" +#include +//#include "proc/nobugcfg.hpp" #include -#include +//#include #include -#include -#include - -#include "lib/scopedholder.hpp" +//#include using std::string; using std::cout; -using std::vector; using boost::format; @@ -37,150 +33,6 @@ using boost::format; } - namespace funny { - - - class Mrpf - : boost::noncopyable - { - long secret_; - - typedef Mrpf _ThisType; - - public: - Mrpf() - : secret_(0) - { - cout << "Mrpf() this=" << this <<"\n"; - } - ~Mrpf() - { - checksum -= secret_; - cout << "~Mrpf() this=" << this <<" skeret="<"< > - class Allocator_TransferNoncopyable - { - typedef Allocator_TransferNoncopyable _ThisType; - - PAR par_; - - - public: - typedef typename PAR::size_type size_type; - typedef typename PAR::difference_type difference_type; - typedef typename PAR::pointer pointer; - typedef typename PAR::const_pointer const_pointer; - typedef typename PAR::reference reference; - typedef typename PAR::const_reference const_reference; - typedef typename PAR::value_type value_type; - - template - struct rebind - { typedef Allocator_TransferNoncopyable<_Tp1, PAR> other; }; - - Allocator_TransferNoncopyable() { } - - Allocator_TransferNoncopyable(const _ThisType& __a) - : par_(__a.par_) { } - Allocator_TransferNoncopyable(const PAR& __a) - : par_(__a) { } - - template - Allocator_TransferNoncopyable(const std::allocator&) { } - - ~Allocator_TransferNoncopyable() { } - - - - size_type max_size() const { return par_.max_size(); } - pointer address(reference r) const { return par_.address(r); } - const_pointer address(const_reference cr) const { return par_.address(cr); } - pointer allocate(size_type n, const void *p=0){ return par_.allocate(n,p); } - void deallocate(pointer p, size_type n) { return par_.deallocate(p,n); } - void destroy(pointer p) { return par_.destroy(p); } - - - void - construct (pointer p, const TY& ref) - { - cout << "Allo::construct( p="<(ref), *p); - } - }; - - template - inline bool - operator== (Allocator_TransferNoncopyable<_T1> const&, Allocator_TransferNoncopyable<_T2> const&) - { return true; } - - template - inline bool - operator!= (Allocator_TransferNoncopyable<_T1> const&, Allocator_TransferNoncopyable<_T2> const&) - { return false; } - - - - typedef Allocator_TransferNoncopyable Allo; int main (int argc, char* argv[]) @@ -188,22 +40,7 @@ main (int argc, char* argv[]) NOBUG_INIT; - vector mrmpf; - - mrmpf.reserve(2); - mrmpf.push_back(funny::Mrpf()); - mrmpf[0].setup(33); - - cout << ".....resize:\n"; - - mrmpf.resize(5); - - - cout << "Summmmmmmmmmmmmmmmmmmm="< #include @@ -40,39 +41,9 @@ namespace lib { using std::map; using std::cout; - - namespace { // yet another test dummy - long checksum = 0; - bool magic = false; - - class Dummy - : boost::noncopyable - { - int val_; - - public: - Dummy () - : val_(1 + (rand() % 100000000)) - { - checksum += val_; - if (magic) - throw val_; - } - - ~Dummy() - { - checksum -= val_; - } - - long add (int i) { return val_+i; } - }; - - - typedef ScopedHolder HolderD; - typedef ScopedPtrHolder PtrHolderD; - - } + typedef ScopedHolder HolderD; + typedef ScopedPtrHolder PtrHolderD; /********************************************************************************** diff --git a/tests/common/scopedholdertransfertest.cpp b/tests/common/scopedholdertransfertest.cpp new file mode 100644 index 000000000..b933ad253 --- /dev/null +++ b/tests/common/scopedholdertransfertest.cpp @@ -0,0 +1,164 @@ +/* + ScopedHolderTransfer(Test) - managing noncopyable objects within a growing vector + + Copyright (C) Lumiera.org + 2008, 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. + +* *****************************************************/ + + + +#include "common/test/run.hpp" +#include "common/util.hpp" + +#include "lib/scopedholder.hpp" +#include "lib/scopedholdertransfer.hpp" +#include "testdummy.hpp" + +#include +#include + + +namespace lib { + namespace test { + + using ::Test; + using util::isnil; + + using std::map; + using std::cout; + + + typedef ScopedHolder HolderD; + typedef ScopedPtrHolder PtrHolderD; + + + + /********************************************************************************** + * @test ScopedHolder and ScopedPtrHolder are initially empty and copyable. + * After taking ownership, they prohibit copy operations, manage the + * lifecycle of the contained object and provide smart-ptr like access. + * A series of identical tests is conducted both with the ScopedPtrHolder + * (the contained objects are heap allocated but managed by the holder) + * and with the ScopedHolder (objects placed inline) + */ + class ScopedHolderTransfer_test : public Test + { + + virtual void + run (Arg arg) + { + + cout << "checking ScopedHolder...\n"; + buildVector(); + growVector(); + checkErrorHandling(); + + cout << "checking ScopedPtrHolder...\n"; + buildVector(); + growVector(); + checkErrorHandling(); + } + + void create_contained_object (HolderD& holder) { holder.create(); } + void create_contained_object (PtrHolderD& holder) { holder.reset(new Dummy()); } + + + template + void + buildVector() + { + ASSERT (0==checksum); + { + UNIMPLEMENTED ("create constant sized vector holding noncopyables"); + HO holder; + ASSERT (!holder); + ASSERT (0==checksum); + + create_contained_object (holder); + ASSERT (holder); + ASSERT (false!=holder); + ASSERT (holder!=false); + + ASSERT (0!=checksum); + ASSERT ( &(*holder)); + ASSERT (holder->add(2) == checksum+2); + + Dummy *rawP = holder.get(); + ASSERT (rawP); + ASSERT (holder); + ASSERT (rawP == &(*holder)); + ASSERT (rawP->add(-5) == holder->add(-5)); + + TRACE (test, "holder at %x", &holder); + TRACE (test, "object at %x", holder.get() ); + TRACE (test, "size(object) = %d", sizeof(*holder)); + TRACE (test, "size(holder) = %d", sizeof(holder)); + } + ASSERT (0==checksum); + } + + + template + void + growVector() + { + ASSERT (0==checksum); + { + UNIMPLEMENTED ("check growing a vector holding noncopyables"); + } + ASSERT (0==checksum); + } + + + template + void + checkErrorHandling() + { + UNIMPLEMENTED ("throw an error while growing"); + ASSERT (0==checksum); + { + HO holder; + + magic = true; + try + { + create_contained_object (holder); + NOTREACHED ; + } + catch (int val) + { + ASSERT (0!=checksum); + checksum -= val; + ASSERT (0==checksum); + } + ASSERT (!holder); /* because the exception happens in ctor + object doesn't count as "created" */ + magic = false; + } + ASSERT (0==checksum); + } + + }; + + LAUNCHER (ScopedHolderTransfer_test, "unit common"); + + + }// namespace test + +} // namespace lib + diff --git a/tests/common/testdummy.hpp b/tests/common/testdummy.hpp new file mode 100644 index 000000000..31db265b3 --- /dev/null +++ b/tests/common/testdummy.hpp @@ -0,0 +1,73 @@ +/* + TESTDUMMY.hpp - yet another test dummy for tracking ctor/dtor calls + + Copyright (C) Lumiera.org + 2008, 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. + +* *****************************************************/ + + + +#include + + +namespace lib { + namespace test { + + namespace { // yet another test dummy + + long checksum = 0; + bool magic = false; + + class Dummy + : boost::noncopyable + { + int val_; + + public: + Dummy () + : val_(1 + (rand() % 100000000)) + { + checksum += val_; + if (magic) + throw val_; + } + + ~Dummy() + { + checksum -= val_; + } + + long add (int i) { return val_+i; } + + protected: + int getVal() const { return val_; } + + void + setVal (int newVal) + { + checksum += newVal - val_; + val_ = newVal; + } + }; + + }// anonymous test dummy + + }// namespace test + +} // namespace lib + diff --git a/tests/common/vectortransfertest.cpp b/tests/common/vectortransfertest.cpp new file mode 100644 index 000000000..b4c415c3e --- /dev/null +++ b/tests/common/vectortransfertest.cpp @@ -0,0 +1,163 @@ +/* + VectorTransfer(Test) - intercept object copying when a STL vector grows + + Copyright (C) Lumiera.org + 2008, 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. + +* *****************************************************/ + + + +#include "common/test/run.hpp" + +#include "lib/scopedholdertransfer.hpp" +#include "testdummy.hpp" + +#include +#include + + +namespace lib { + namespace test { + + using ::Test; + using std::vector; + using std::cout; + + namespace { // extending the Dummy for our special purpose.... + + class TransDummy + : public Dummy + { + public: + TransDummy() + { + TRACE (test, "CTOR TransDummy() --> this=%x", this); + setVal(0); // we use val_==0 to mark the "empty" state + } + + ~TransDummy() + { + TRACE (test, "DTOR ~TransDummy() this=%x", this); + } + + /* to make Dummy usable within vector, we need to provide + * \em special copy operations, an operator bool() and + * a transfer_control friend function to be used by + * our special allocator. + */ + + TransDummy (const TransDummy& o) + { + TRACE (test, "COPY-ctor TransDummy( ref=%x ) --> this=%x", &o,this); + ASSERT (!o, "protocol violation: real copy operations inhibited"); + } + + TransDummy& + operator= (TransDummy const& ref) + { + TRACE (test, "COPY target=%x <-- source=%x", this,&ref); + ASSERT (!(*this)); + ASSERT (!ref, "protocol violation: real copy operations inhibited"); + return *this; + } + + void + setup (int x=0) + { + setVal (x? x : (rand() % 10000)); + TRACE (test, "CREATE val=%d ---> this=%x", getVal(),this); + } + + + // define implicit conversion to "bool" the naive way... + operator bool() const + { + return 0!=getVal(); + } + + + friend void transfer_control (TransDummy& from, TransDummy& to); + + }; + + + void + transfer_control (TransDummy& from, TransDummy& to) + { + TRACE (test, "TRANSFER target=%x <-- source=%x", &to,&from); + ASSERT (!to, "protocol violation: target already manages another object"); + to.setVal (from.getVal()); + from.setVal(0); + } + + typedef Allocator_TransferNoncopyable Allo; + typedef vector TransDummyVector; + } + + + + + /********************************************************************************** + * @test ScopedHolder and ScopedPtrHolder are initially empty and copyable. + * After taking ownership, they prohibit copy operations, manage the + * lifecycle of the contained object and provide smart-ptr like access. + * A series of identical tests is conducted both with the ScopedPtrHolder + * (the contained objects are heap allocated but managed by the holder) + * and with the ScopedHolder (objects placed inline) + */ + class VectorTransfer_test : public Test + { + + virtual void + run (Arg arg) + { + cout << "\n..setup table space for 2 elements\n"; + TransDummyVector table; + table.reserve(2); + ASSERT (0==checksum); + + cout << "\n..install one element at index[0]\n"; + table.push_back(TransDummy()); + ASSERT (0==checksum); + + table[0].setup(); // switches into "managed" state + ASSERT (0 < checksum); + int theSum = checksum; + + cout << "\n..*** resize table to 5 elements\n"; + table.resize(5); + ASSERT (theSum==checksum); + + cout << "\n..install another element\n"; + table[3].setup(375); + ASSERT (theSum+375==checksum); + + cout << "\n..kill all elements....\n"; + table.clear(); + ASSERT (0==checksum); + } + + }; + + LAUNCHER (VectorTransfer_test, "unit common"); + + + }// namespace test + +} // namespace lib +