Search code examples
c++templatestype-inferenceraii

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


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 initializes 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.


Solution

  • 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.