create a lib header based on this solution

This commit is contained in:
Fischlurch 2008-10-26 22:35:01 +01:00
parent 32637dd958
commit 6a1c33e5a1
8 changed files with 599 additions and 216 deletions

View file

@ -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<B> _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<ScopedPtrHolder>... from=%x to=%x",&from, &to);
must_be_null (to);
to.swap(from);
}
};
@ -133,11 +147,22 @@ namespace lib {
typedef ScopedHolder<TY> _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<ScopedHolder>... 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;
}
}
};

View file

@ -0,0 +1,137 @@
/*
SCOPEDHOLDERVECTOR.hpp - using ScopedHolder within a STL vector
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
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 <memory>
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
* <tt> transfer_control(TY& from, TY& to)</tt> intended
* to be found by ADL. Note: in case this function throws,
* it <i>must not have any side effects</i>.
* - besides, the \em noncopyable type needs to provide an
* <tt>operator bool()</tt> 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 TY, class PAR = std::allocator<TY> >
class Allocator_TransferNoncopyable
{
typedef Allocator_TransferNoncopyable<TY,PAR> _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<typename XX>
struct rebind
{ typedef Allocator_TransferNoncopyable<XX, PAR> other; };
Allocator_TransferNoncopyable() { }
Allocator_TransferNoncopyable(const _ThisType& allo)
: par_(allo.par_) { }
Allocator_TransferNoncopyable(const PAR& allo)
: par_(allo) { }
template<typename X>
Allocator_TransferNoncopyable(const std::allocator<X>&) { }
~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<TY&>(ref), *p);
}
};
template<typename TY1, typename TY2, class ALO>
inline bool
operator== (Allocator_TransferNoncopyable<TY1,ALO> const&, Allocator_TransferNoncopyable<TY2, ALO> const&)
{ return true; }
template<typename TY1, typename TY2, class ALO>
inline bool
operator!= (Allocator_TransferNoncopyable<TY1,ALO> const&, Allocator_TransferNoncopyable<TY2,ALO> const&)
{ return false; }
} // namespace lib
#endif

View file

@ -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...
]

View file

@ -14,20 +14,16 @@
// 10/8 - abusing the STL containers to hold noncopyable values
//#include <nobug.h>
#include "proc/nobugcfg.hpp"
#include <nobug.h>
//#include "proc/nobugcfg.hpp"
#include <iostream>
#include <typeinfo>
//#include <typeinfo>
#include <boost/format.hpp>
#include <boost/noncopyable.hpp>
#include <vector>
#include "lib/scopedholder.hpp"
//#include <boost/noncopyable.hpp>
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="<<secret_<<"\n";
}
Mrpf (const Mrpf& o)
: secret_(0)
{
cout << "Mrpf( ref ) this=" <<this << " o="<<&o<<"\n";
ASSERT (!o.secret_);
}
Mrpf&
operator= (Mrpf const& ref)
{
cout << "Mrpf=ref this=" <<this << " ref="<<&ref<<"\n";
ASSERT (!(*this));
ASSERT (!ref);
return *this;
}
void
setup (long x=0)
{
secret_ = (x? x : (rand() % 10000));
checksum += secret_;
cout << "this="<<this<<" ....setup-->"<<secret_<<"\n";
}
long getIt() const { return secret_; }
typedef long _ThisType::*unspecified_bool_type;
/** implicit conversion to "bool" */
operator unspecified_bool_type() const // never throws
{
return secret_? &_ThisType::secret_ : 0;
}
bool operator! () const { return 0==secret_; }
protected:
friend void transfer_control (Mrpf& from, Mrpf& to);
};
void
transfer_control (Mrpf& from, Mrpf& to)
{
ASSERT (!to.secret_);
cout << "transfer... from="<<&from<<" to="<<&to<<"\n";
to.secret_ = from.secret_;
from.secret_ = 0;
}
}
template<class TY, class PAR = std::allocator<TY> >
class Allocator_TransferNoncopyable
{
typedef Allocator_TransferNoncopyable<TY,PAR> _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<typename _Tp1>
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<typename X>
Allocator_TransferNoncopyable(const std::allocator<X>&) { }
~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="<<p<<", ref="<<&ref<<" )\n";
new(p) TY();
ASSERT (p);
ASSERT (!(*p));
if (ref)
transfer_control (const_cast<TY&>(ref), *p);
}
};
template<typename _T1, typename _T2>
inline bool
operator== (Allocator_TransferNoncopyable<_T1> const&, Allocator_TransferNoncopyable<_T2> const&)
{ return true; }
template<typename _T1, typename _T2>
inline bool
operator!= (Allocator_TransferNoncopyable<_T1> const&, Allocator_TransferNoncopyable<_T2> const&)
{ return false; }
typedef Allocator_TransferNoncopyable<funny::Mrpf> Allo;
int
main (int argc, char* argv[])
@ -188,22 +40,7 @@ main (int argc, char* argv[])
NOBUG_INIT;
vector<funny::Mrpf, Allo> mrmpf;
mrmpf.reserve(2);
mrmpf.push_back(funny::Mrpf());
mrmpf[0].setup(33);
cout << ".....resize:\n";
mrmpf.resize(5);
cout << "Summmmmmmmmmmmmmmmmmmm="<<checksum<<"\n";
mrmpf.clear();
cout << "Summmmmmmmmmmmmmmmmmm="<<checksum<<"\n";
cout << "\ngulp\n";
cout << "\n.gulp.\n";
return 0;

View file

@ -26,6 +26,7 @@
#include "common/util.hpp"
#include "lib/scopedholder.hpp"
#include "testdummy.hpp"
#include <boost/noncopyable.hpp>
#include <iostream>
@ -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<Dummy> HolderD;
typedef ScopedPtrHolder<Dummy> PtrHolderD;
}
typedef ScopedHolder<Dummy> HolderD;
typedef ScopedPtrHolder<Dummy> PtrHolderD;
/**********************************************************************************

View file

@ -0,0 +1,164 @@
/*
ScopedHolderTransfer(Test) - managing noncopyable objects within a growing vector
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
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 <iostream>
#include <vector>
namespace lib {
namespace test {
using ::Test;
using util::isnil;
using std::map;
using std::cout;
typedef ScopedHolder<Dummy> HolderD;
typedef ScopedPtrHolder<Dummy> 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<Dummy>...\n";
buildVector<HolderD>();
growVector<HolderD>();
checkErrorHandling<HolderD>();
cout << "checking ScopedPtrHolder<Dummy>...\n";
buildVector<PtrHolderD>();
growVector<PtrHolderD>();
checkErrorHandling<PtrHolderD>();
}
void create_contained_object (HolderD& holder) { holder.create(); }
void create_contained_object (PtrHolderD& holder) { holder.reset(new Dummy()); }
template<class HO>
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<class HO>
void
growVector()
{
ASSERT (0==checksum);
{
UNIMPLEMENTED ("check growing a vector holding noncopyables");
}
ASSERT (0==checksum);
}
template<class HO>
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

View file

@ -0,0 +1,73 @@
/*
TESTDUMMY.hpp - yet another test dummy for tracking ctor/dtor calls
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
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 <boost/noncopyable.hpp>
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

View file

@ -0,0 +1,163 @@
/*
VectorTransfer(Test) - intercept object copying when a STL vector grows
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
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 <iostream>
#include <vector>
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<TransDummy> Allo;
typedef vector<TransDummy, Allo> 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