I would like to write an object generator for a templated RAII class -- basically a function template to construct an object using type deduction of parameters so the types don't have to be specified explicitly.
The problem I foresee is that the helper function that takes care of type deduction for me is going to return the object by value, which will (**) result in a premature call to the RAII destructor when the copy is made. Perhaps C++0x move semantics could help but that's not an option for me.
Anyone seen this problem before and have a good solution?
This is what I have:
template<typename T, typename U, typename V>
class FooAdder
{
private:
typedef OtherThing<T, U, V> Thing;
Thing &thing_;
int a_;
// many other members
public:
FooAdder(Thing &thing, int a);
~FooAdder();
FooAdder &foo(T t, U u);
FooAdder &bar(V v);
};
The gist is that OtherThing
has a horrible interface, and FooAdder
is supposed to make it easier to use. The intended use is roughly like this:
FooAdder(myThing, 2)
.foo(3, 4)
.foo(5, 6)
.bar(7)
.foo(8, 9);
The FooAdder
constructor initia开发者_开发百科lizes some internal data structures. The foo
and bar
methods populate those data structures. The ~FooAdder
dtor wraps things up and calls a method on thing_
, taking care of all the nastiness.
That would work fine if FooAdder
wasn't a template. But since it is, I would need to put the types in, more like this:
FooAdder<Abc, Def, Ghi>(myThing, 2) ...
That's annoying, because the types can be inferred based on myThing
. So I would prefer to create a templated object generator, similar to std::make_pair
, that will do the type deduction for me. Something like this:
template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
return FooAdder<T, U, V>(thing, a);
}
That seems problematic: because it returns by value, the stack temporary object will (**) be destructed, which will cause the RAII dtor to run prematurely.
** - if RVO is not implemented. Most compilers do, but it is not required, and can be turned off in gcc using -fno-elide-constructors
.
It seems pretty easy. The questioner himself proposed a nice solution, but he can just use a usual copy constructor with a const-reference parameter. Here is what i proposed in comments:
template<typename T, typename U, typename V>
class FooAdder
{
private:
mutable bool dismiss;
typedef OtherThing<T, U, V> Thing;
Thing &thing_;
int a_;
// many other members
public:
FooAdder(Thing &thing, int a);
FooAdder(FooAdder const&o);
~FooAdder();
FooAdder &foo(T t, U u);
FooAdder &bar(V v);
};
FooAdder::FooAdder(Thing &thing, int a)
:thing_(thing), a_(a), dismiss(false)
{ }
FooAdder::FooAdder(FooAdder const& o)
:dismiss(false), thing_(o.thing_), a_(o.a_)
{ o.dismiss = true; }
FooAdder::~FooAdder() {
if(!dismiss) { /* wrap up and call */ }
}
It Just Works.
template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
return FooAdder<T, U, V>(thing, a);
}
int main() {
AddFoo(myThing, 2)
.foo(3, 4)
.foo(5, 6)
.bar(7)
.foo(8, 9);
}
No need for complex templates or smart pointers.
You'll need a working copy constructor, but optimizing out such copies is explicitly allowed in the standard and should be quite a common optimization for compilers to make.
I'd say there's probably very little need to worry about the move semantics here (it is possible that it won't work anyway - see the auto_ptr_ref
hackery that it takes for std::auto_ptr
).
If you want to guarantee that what you want to do will work without using move semantics you need to do what auto_ptr
does, which is maintain ownership state and provide a conversion operator to a type that transfers ownership between auto_ptrs
.
In your case:
- Add a mechanism to indicate ownership in
FooAdder
. InFooAdder's
destructor, only call the cleanup function if it has ownership. - Privatize the copy constructor that takes a
const FooAdder &
; this prevents the compiler from using the copy constructor on rvalues which would violate your single owner invariant. - Create an auxilary type (say,
FooAdderRef
) that will be used to transfer ownership betweenFooAdders
. It should contain enough information to transfer ownership. - Add a conversion operator (
operator FooAdderRef
) toFooAdder
that relinquishes ownership inFooAdder
and returns aFooAdderRef
. - Add a constructor that takes a
FooAdderRef
and claims ownership from it.
This is identical to what auto_ptr
does in case you'd want to look at a real implementation. It prevents arbitrary copying from violating your RAII constraints while allowing you to specify how to transfer ownership from factory functions.
This is also why C++0x has move semantics. Because it's a giant PITA otherwise.
A friend template? (tested with gcc only)
template <class T, class U, class V> struct OtherThing
{
void init() { }
void fini() { }
};
template <class T, class U, class V>
class Adder
{
private:
typedef OtherThing<T, U, V> Thing;
Thing& thing_;
int a_;
Adder( const Adder& );
Adder& operator=( const Adder& );
Adder( Thing& thing, int a ) : thing_( thing ), a_( a ) {}
public:
~Adder() { thing_.fini(); }
Adder& foo( T, U ) { return *this; }
Adder& bar( V ) { return *this; }
template <class X, class Y, class Z> friend
Adder<X,Y,Z> make_adder( OtherThing<X,Y,Z>&, int );
};
template <class T, class U, class V>
Adder<T,U,V> make_adder( OtherThing<T,U,V>& t, int a )
{
t.init();
return Adder<T,U,V>( t, a );
}
int main()
{
OtherThing<int, float, char> ot;
make_adder( ot, 10 ).foo( 1, 10.f ).bar( 'a'
).foo( 10, 1 ).foo( 1, 1 ).bar( '0' );
return 0;
}
Since C++03 requires explicitly spelling out the type in every declaration, there's no way to accomplish that without dynamic typing, eg having the template inherit from an abstract base class.
You did get something clever with
AddFoo(myThing, 2) // OK: it's a factory function
.foo(3, 4)
.foo(5, 6)
.bar(7)
.foo(8, 9); // but object would still get destroyed here
but it will be too much of a pain to code everything in that chain of calls.
C++0x adds auto
type deduction, so look into upgrading your compiler, or enabling it if you have it. (-std=c++0x
on GCC.)
EDIT: If the above syntax is OK but you want to have several chains in a scope, you could define a swap
with void*
operation.
// no way to have a type-safe container without template specification
// so use a generic opaque pointer
void *unknown_kinda_foo_handle = NULL;
CreateEmptyFoo(myThing, 2) // OK: it's a factory function
.foo(3, 4)
.foo(5, 6)
.bar(7)
.foo(8, 9)
.swap( unknown_kinda_foo_handle ) // keep object, forget its type
; // destroy empty object (a la move)
// do some stuff
CreateEmptyFoo(myThing, 2) // recover its type (important! unsafe!)
.swap( unknown_kinda_foo_handle ) // recover its contents
.bar( 9 ) // do something
; // now it's destroyed properly.
This is terribly unsafe, but appears to fit your requirements perfectly.
EDIT: swap
with a default-constructed object is also the answer to emulating move
in C++03. You need to add a default constructor, and perhaps a resource-free default state wherein the destructor does nothing.
Here's one solution, but I suspect there are better options.
Give FooAdder
a copy ctor with something similar to std::auto_ptr
's move semantics. To do this without dynamic memory allocation, the copy ctor can set a flag to indicate that the dtor shouldn't do the wrap-up. Like this:
FooAdder(FooAdder &rhs) // Note: rhs is not const
: thing_(rhs.thing_)
, a_(rhs.a_)
, // etc... lots of other members, annoying.
, dismiss_(false)
{
rhs.dismiss_ = true;
}
~FooAdder()
{
if (!dismiss_)
{
// do wrap-up here
}
}
It's probably sufficient to disable the assignment operator by making it private -- shouldn't be any need to call it.
When I consider problems like this, I usually prefer to think of the interface I wish to have first:
OtherThing<T,U,V> scopedThing = FooAdder(myThing).foo(bla).bar(bla);
I would propose a very simple solution:
template <class T, class U, class V>
class OtherThing: boost::noncopyable
{
public:
OtherThing(); // if you wish
class Parameters // may be private if FooAdder is friend
{
public:
template<class,class,class> friend class OtherThing;
Parameters(int,int,int);
Parameters(const Parameters& rhs); // proper resource handling
~Parameters(); // proper resource handling
private:
Parameters& operator=(const Parameters&); // disabled
mutable bool dismiss; // Here is the hack
int p1;
int p2;
int p3;
}; // Parameters
OtherThing(const Parameters& p);
};
And then:
template <class T, class U, class V>
OtherThing<T,U,V>::Parameters fooAdder(Thing<T,U,V> thing, bla_type, bla_type);
There is no need for conversion operators and the like with which you risk to alter the noncopyable semantics, simply create a temporary struct from which your final class is constructible that will be used to pass all the parameters and alter the semantics of this struct for proper RAII. This way the final class OtherThing
does not have screwed semantics and the nastiness (dismiss
boolean) is safely tucked in a temporary that should never be exposed anyway.
You still need to make sure for proper exception handling. Notably it means that the temporary struct
is responsible for the resource as long as it's not passed to OtherThing
.
I know it does not seem to bring much to the table since you're basically going to hack Parameters
instead of OtherThing
, but I urge you to consider what this would mean:
OtherThing<T,U,V> scopedThing = /**/;
OtherThing<T,U,V>* anotherThing = new OtherThing<T,U,V>(scopedThing);
The second line is valid with your tentative hacks, since scopedThing
can be taken by reference as well as const reference, but it does screw things up as much as it does with std::auto_ptr
. In the same vein, you can then have std::vector< OtherThing<T,U,V> >
and the compiler is never going to complain...
精彩评论