Best way to write an object generator for an RAII template class?

I would like to write an object generator for the RAII template class - basically a function template for constructing an object using the type inference method, t should be specified explicitly.

The problem that I foresee is that a helper function that takes care of type inference for me will return the object by value, which (**) will cause the RAII destructor to be called prematurely when copying. Maybe C ++ 0x move semantics might help, but this is not an option for me.

Has anyone seen this problem before and has 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 bottom line is that OtherThing has a terrible interface, and FooAdder should make it easier to use. Intended use is something like this:

 FooAdder(myThing, 2) .foo(3, 4) .foo(5, 6) .bar(7) .foo(8, 9); 

The FooAdder constructor initializes some internal data structures. The foo and bar methods populate these data structures. ~FooAdder dtor wraps things and calls a method on thing_ , taking care of all the nasty things.

This will work fine if FooAdder not a template. But since this is the case, I will need to introduce types that are more similar to this:

 FooAdder<Abc, Def, Ghi>(myThing, 2) ... 

This is annoying because types can be inferred based on myThing . Therefore, I would prefer to create a template object generator similar to std::make_pair , which will std::make_pair type inference for me. Something like that:

 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); } 

This seems problematic: since it returns a value, the temporary stack object will be (**) destroyed, which will cause the RAI dtor to start prematurely.

** - if RVO is not implemented. Most compilers do, but this is not required, and you can disable them in gcc using -fno-elide-constructors .

+4
source share
7 answers

It looks pretty easy. The questionnaire itself suggested a good solution, but it can simply use the usual copy constructor with the const-reference parameter. Here is what I suggested in the 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.

+3
source

You will need a working constructor, but optimization of such copies is explicitly allowed in the standard and should be a fairly general optimization for compilers.

I would say that there is probably very little need to worry about the semantics of movement here (it is possible that this will not work - see the auto_ptr_ref that is required for std::auto_ptr ).

+1
source

If you want to guarantee that what you want to do will work without using the semantics of movement, you need to do what auto_ptr does, which maintains the state of ownership and provides a conversion operator to a type that transfers ownership between auto_ptrs .

In your case:

  • Add a mechanism indicating ownership of FooAdder . In the FooAdder's destructor, call only the cleanup function, if it has ownership.
  • Privatize the copy constructor that accepts const FooAdder & ; this prevents the compiler from using the copy constructor on rvalues, which will violate your only invariant owner.
  • Create a helper type (for example, FooAdderRef ) that will be used to transfer ownership between FooAdders . It should contain enough information to transfer ownership.
  • Add a conversion operator FooAdderRef ( operator FooAdderRef ) to FooAdder , which renounces ownership in FooAdder and returns FooAdderRef .
  • Add a constructor that accepts FooAdderRef and requires ownership.

This is identical to what auto_ptr does if you want to look at a real implementation. This prevents arbitrary copying from violating RAII restrictions, letting you specify how to transfer ownership of factory functions.

This is why C ++ 0x has move semantics. Because it's a giant PITA otherwise.

+1
source

Since C ++ 03 requires an explicit type declaration in each declaration, there is no way to do this without dynamic typing, for example, inheriting a template from an abstract base class.

You got something smart with

 AddFoo(myThing, 2) // OK: it 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 painful to encode everything in this call chain.

C ++ 0x adds auto type output, so check your compiler update or enable it if you have one. ( -std=c++0x on GCC.)

EDIT: If the above syntax is fine, but you want to have multiple chains in scope, you can define a swap operation with void* .

  // 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 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 destroyed properly. 

This is terribly dangerous, but seems to perfectly fit your requirements.

EDIT : swap with a default built object is also a response to the emulation of move in C ++ 03. You need to add a default constructor and, possibly, a free default state in which the destructor does nothing.

0
source

Here is one solution, but I suspect there are better options.

Give FooAdder copy of ctor with something similar to the semantics of moving std::auto_ptr . To do this without allocating dynamic memory, a copy of ctor may set a flag indicating that dtor should not complete completion. 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 is probably enough to disable the assignment operator, making it private - no need to call it.

0
source

Friend pattern? (tested only with gcc)

 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; } 
0
source

When I look at such issues, I usually prefer the interface that I want to have first:

 OtherThing<T,U,V> scopedThing = FooAdder(myThing).foo(bla).bar(bla); 

I would suggest 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 the help of which you risk changing the semantics that cannot be copied, just create a temporary structure from which your last class will be created, which will be used to pass all the parameters and change the semantics of this struct for the correct RAII . Thus, the last class of OtherThing does not have screwed-up semantics, and muck ( dismiss boolean) is safely tucked into a temporary one that should never be affected.

You still need to make sure that the exception handling is correct. In particular, this means that the temporary struct is responsible for the resource if it is not passed to OtherThing .

I know this doesn't look like a table, since you are basically going to hack Parameters instead of OtherThing , but I urge you to think what this will mean:

 OtherThing<T,U,V> scopedThing = /**/; OtherThing<T,U,V>* anotherThing = new OtherThing<T,U,V>(scopedThing); 

The second line is valid with your preliminary hacks, since scopedThing can be taken either by reference or by const, but it still blames things the same way as with std::auto_ptr . In the same vein, you can have std::vector< OtherThing<T,U,V> > , and the compiler will never complain ...

0
source

Source: https://habr.com/ru/post/1308396/


All Articles