Sometimes I want to separate the declaration and construction of a local variable
{ Boost.Optional is almost exactly what you're looking for. -mod/sk }
I have this problem with c++ syntax. The problem is basically that the
declaration (and scope) of a local variable is bound to its
construction (in time, flow and scope). Sometimes (examples below) I
want to declare a local variable for use in a certain execution scope,
but I want to invoke its contructor within a more inner scope. So, it
looks like I need to separate the construction from the declaration.
The regular syntax of c++ gives just one expression to both declare
and construct the local variable, but not just one of them. This
problem is more apparent with classes that don't have a resourceless
state - so you can't construct an "empty" object and initialize later.
I thought of a way to do it nicely with a template class that I named
ecStrictPlace (it uses placement new). Instead of defining a variable
of type X, I define a variable of type ecStrictPlace<X> which holds
the place in memory where an object of class X can be constructed
later using one of X's constructors, and then the object can be used.
When the execution exits the scope, the local ecStrictPlace<X>
destructor also destroys the internal X object (if it has actually
been contstructed). So this should also be exception safe.
I tried to look for something like it in the newsgroups and in boost
but found nothing.
The purpose of the post is:
1) To find out whether this is a well-known problem with some well-
known solution (with the same approach as mine or other, as I said I
tried to look for it). If it is not well-known problem, how come? It
doesn't happen many times, but it does happen once in a while. For
example I came to it about 5 times just in the past month.
2) To see what you people think about my approach, and verify that it
really is safe and good as I think it is.
3) Are there more efficient solutions? With my solution the overhead
is a bool variable, a member of ecStrictPlace, which signals whether
the object has been constructed. And then some if-statements that
access this bool. I can't see how to avoid this overhead while keeping
exception safety.
4) Are there solutions with better syntax/usage? In the following
examples I added a pseudo code of how I wished it could be done in c+
+, to me it seems the best syntax for it, note that the code that uses
my solution almost exactly matches.
Now I will demostrate the situations where my problem occurrs (which
will explain why I actually want to do this). I'll explain what I want
to do, then show the code how I wished I could write it in c++, how I
do it with my approach, and other ways to do it. After the examples
there is the code of my template class within a test program that uses
it (compiled with g++ and executed). The code in these examples was
not checked for compilation, its purpose is just to demonstrate the
situations where this issue occurs.
example 1:
I have some class X with a constructor X::X( Y& ). It expects the
parameter Y to be alive while X::X( Y& ) executes but no neccessarily
longer than that. Lets say that Y is a RAII that keeps a certain
resource locked (which is needed to be locked for the construction of
any X instance, but not later - not for the whole life of the
instance).
So, the function in the example needs to construct variable x of type
X using constructor X::X( Y& ), then to use x in some code. In this
example the usage of variable x is just invoking x.use(), but think of
it more generally as the place where some code that uses variable x is
present.
So, I wished my code would look like (pseudo syntax):
void example1()
{
declare X x; //local variable x is declared in this scope, but not
constructed yet
{
Y y;
construct X x(y); /*local variable x is contructed inside this inner
scope because the constructor needs a resource Y.*/
/*y should release its resource now.*/
}
//now I can use the object in x
x.use();
//...more code that uses x...
}
using my template class ecStrictPlace it looks like:
void example1()
{
ecStrictPlace< X > x; //keeps place (on the stack!) to construct an
object of type X later
{
Y y;
x.construct(y); /*local variable x is contructed inside this inner
scope because the constructor needs a resource Y.*/
/*y should release its resource now.*/
}
//now I can use the object
x.get().use();
//...more code that uses x.get()
}
The following code does the thing but overheads with a free store
allocation+deallocation.
void example1()
{
std::auto_ptr< X > x;
{
Y y;
x = std::auto_ptr< X >( new X(y) );
}
x->use();
//...more code that uses *x
}
The following does the same thing with no overhead (not even my extra
bool member data), but the code cannot be written so straight forward.
void example1()
{
class X_construction
{
private:
Y _y;
X _x;
public:
X_construction()
:
_y(),
_x(_y)
{}
X& get()
{
return _x;
}
};
X_construction x;
x.get().use();
//...more code that uses x.get()
}
example 2:
I need to construct an object of type X with X::X( Y& ) or with
X::X( Z& ) and then use it. The choice of the constructor depends on
some parameter.
pseudo code:
void example2( bool howToConstruct )
{
declare X x; //local variable x is declared in this scope, but not
constructed yet
if( howToConstruct )
{
Y y;
construct X x(y);
}
else
{
Z z;
construct X x(z);
}
x.use();
//more code that uses x...
}
code using my solution:
void example2( bool howToConstruct )
{
ecStrictPlace< X > x; //local variable x is declared in this scope,
but not constructed yet
if( howToConstruct )
{
Y y;
x.construct(y);
}
else
{
Z z;
x.construct(z);
}
x.get().use1();
x.get().use2();
//more code that uses the object as x.get()
}
Following is my code for this template class ecStrictPlace within a
test program that uses it. I compiled with g++ and run. Its written
with my usual identifier naming convention. Each identifier begins
with a sequence of lower case characters that states what it is:
class, template class, member function, member template function,
member virtual function, member static function, member data, local
variable, static variable, function parameter.
template class ecStrictPlace< typename rc > Is the class to be used.
It holds a place for an object of type rc.
It has the following member functions:
void ecStrictPlace<rc>::fConstruct( <some parameters> ) will construct
an object of type rc, invoking a constructor of rc by forwarding the
given parameters to the matching constructor - can be called only
once. Will pass on exceptions thrown by rc's constructor, and leave
the object unconstructed.
rc& ecStrictPlace<rc>::fGet() can be called to access the object after
it has been constructed.
When destructing, ecStrictPlace<rc> will destruct the rc object if it
has been constructed.
//////// code starts here
#include <cassert>
#include <iostream>
/* the buffer of char[] that is allocated withing ecRawPlaceInternal
(which is usually on the stack) needs to be of the correct size which
depends on sizeof(rc) and the alignment rules of the compiler. It
isn't my intent to discuss the alignment issue of placement new here,
there are many threads that discuss it (I'll get to read them and
optimize this), so for now I just take the following way-too-big
buffer size, so this can compile and execute on any compiler. */
#define REQUIRED_ALLOCATION_SIZE( drType ) (16+2*sizeof(drType))
/* This template class just hold a memory buffer for construction of
an instance of rc. */
template< typename rc >
class ecRawPlaceInternal
{
public: typedef rc c;
private: typedef char tRawMemory[ REQUIRED_ALLOCATION_SIZE(rc) ];
private: //data
tRawMemory m;
//privates
private: rc* fTypedPtr() const
{
return (reinterpret_cast<rc*>(const_cast<char*>(m)));
}
//methods
public: void* fVoidPtr() const
{
return const_cast<void*>(reinterpret_cast<void
const*>(fTypedPtr()));
}
public: rc* fGetPtr() const
{
return fTypedPtr();
}
public: rc& fGet() const
{
return *(fTypedPtr());
}
//static functions
public: static size_t gRawSize()
{
return sizeof(tRawMemory);
}
};
/* This template class hold a place for one object of typename rc, and
exposes interface for construction and destruction (possibly more than
once). It uses placement new and delete to do so. */
template< typename rc >
class ecRawPlace
{
public: typedef rc c;
private: typedef ecRawPlaceInternal< rc > cInternal;
private: //data
cInternal m;
//methods
public: void fDestruct()
{
m.fGetPtr()->~rc();
}
public: void fConstruct()
{
new( *this ) rc();
}
public: template< typename riX0 > void fConstruct( riX0& rX0 )
{
new( *this ) rc( rX0 );
}
public: template< typename riX0, typename riX1 > void
fConstruct( riX0& rX0, riX1& rX1 )
{
new( *this ) rc( rX0, rX1 );
}
// .. so on, forwarding for longer parameter list
public: rc& fGet() const
{
//assuming an object is constructed in the buffer
return m.fGet();
}
//static functions
public: static size_t gRawSize()
{
return cInternal::gRawSize();
}
// for use by the specific operator new/delete
public: void* fLocationForOperatorNew()
{
return m.fVoidPtr();
}
public: void fDelete()
{
}
};
template< typename rc >
void* operator new( size_t rSize, ecRawPlace< rc >& roPlace ) throw()
{
return roPlace.fLocationForOperatorNew();
}
template< typename rc >
void operator delete( void*, ecRawPlace<rc>& roPlace ) throw()
{
roPlace.fDelete();
}
template< typename rc >
class ecStrictPlace
{
public: typedef rc c;
private: //data
ecRawPlace< rc > m;
bool mConstucted;
public: //xtors
~ecStrictPlace()
{
if( mConstucted ) {
m.fDestruct();
}
}
explicit ecStrictPlace()
:
m(),
mConstucted(false)
{
}
//methods:
private: void fBeginConstruct()
{
assert(!mConstucted);
}
private: void fEndConstruct()
{
mConstucted = true;
}
public: rc& fGet() const
{
assert(mConstucted);
return m.fGet();
}
public: void fConstruct()
{
fBeginConstruct();
m.fConstruct();
fEndConstruct();
}
public: template< typename riX0 > void fConstruct( riX0& rX0 )
{
fBeginConstruct();
m.fConstruct(rX0);
fEndConstruct();
}
public: template< typename riX0, typename riX1 > void
fConstruct( riX0& rX0, riX1& rX1 )
{
fBeginConstruct();
m.fConstruct(rX0,rX1);
fEndConstruct();
}
// .. so on, forwarding for longer parameter list
};
//simple class to help testing ecStrictPlace
class cTestX0
{
private: //data
int m;
public: //xtors
explicit cTestX0()
:
m(-1)
{
}
explicit cTestX0( int r )
:
m(r)
{
}
explicit cTestX0( int, int r )
:
m(r)
{
}
//methods
void fSetValue( int r )
{
m = r;
}
int fGetValue() const
{
return m;
}
};
int main(int argc, char* argv[])
{
{
int aX0( 2 );
int aX1( 3 );
{ //construct a const object with 1 parameter to the contructor, and
read it
ecStrictPlace< cTestX0 const > aPlaceTest;
{
aPlaceTest.fConstruct( aX0 );
}
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
assert( aPlaceTest.fGet().fGetValue() == aX0 );
}
{ //construct a const object with 2 parameter to the contructor, and
read it
ecStrictPlace< cTestX0 const > aPlaceTest;
{
aPlaceTest.fConstruct( aX1, aX0 );
}
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
assert( aPlaceTest.fGet().fGetValue() == aX0 );
}
{ //construct a const object with 0 parameter to the contructor, and
read it
ecStrictPlace< cTestX0 const > aPlaceTest;
{
aPlaceTest.fConstruct();
}
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
assert( aPlaceTest.fGet().fGetValue() == -1 );
}
{ //construct a non-const object, read and write to it
ecStrictPlace< cTestX0 > aPlaceTest;
{
aPlaceTest.fConstruct( aX0 );
}
assert( aPlaceTest.fGet().fGetValue() == aX0 );
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
aPlaceTest.fGet().fSetValue( aX1 );
assert( aPlaceTest.fGet().fGetValue() == aX1 );
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
}
try
{ //exception safe after object constructed
ecStrictPlace< cTestX0 const > aPlaceTest;
{
aPlaceTest.fConstruct( aX1, aX0 );
}
throw 1;
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
assert( aPlaceTest.fGet().fGetValue() == aX0 );
}
catch(...)
{}
try
{ //exception safe before object constructed
ecStrictPlace< cTestX0 const > aPlaceTest;
throw 1;
{
aPlaceTest.fConstruct( aX1, aX0 );
}
std::cout << aPlaceTest.fGet().fGetValue() << "\n";
assert( aPlaceTest.fGet().fGetValue() == aX0 );
}
catch(...)
{}
}
return 0;
}
thanks
itaj sherman
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]