5
\$\begingroup\$

I've made a quick finally type in C++:

template<class F>
class finally_type {
public:

    explicit finally_type(F f) : function(f) {}
    ~finally_type() { try { function(); } catch (...) {} }

private:

    F function;

};


template<class F>
finally_type<F> finally(F f) { return finally_type<F>(f); }

(Edited to add swallower to prevent exceptions from propagating out of the callback.)

With C++0x, it's convenient to use this class as follows:

void test() {

    int* i = new int;

    auto cleanup = finally([i]() { delete i; });

    may_throw();

}

Regardless of whether may_throw() throws, the finally ensures that the cleanup code executes when the stack is unwound. The equivalent idiom in C++03 is using a local type:

void test() {

    int* i = new int;

    struct finally {
        finally(int* i) : i(i) {}
        ~finally() { delete i; }
        int* i;
    } cleanup(i);

    may_throw();

}

As you can see, the closure must be managed explicitly, with loads of repetitive (and consequently error-prone) code; with a lambda, this process is vastly simplified.

Frankly, I don't see much use to it, since if you're interfacing with legacy code, you're probably just going to wrap the types or use a shared_ptr with a custom deleter. You could use it to manage low-level locks in multithreaded code, if that's your fancy for some reason.

Anyway, assuming someone does find it useful, is there anything important that I missed?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Bear in mind that code inside the function called by finally must not allow exceptions to propagate out, or you end up with problems when the destructor is called while cleaning up an exception in the calling code. You might want to put a catch(...) block in the finally_type destructor for that reason.

I agree that the C++98 solution is much uglier, to the point of not being usable. C++98 is never going to be able to capture arbitrary code for execution later. I'm not so sure this is a problem though, since I expect the cleanup code to have to be specialised only per-type, not per-usage. I want the class to be responsible for cleaning itself up, not the client of the class, since the latter can lead to mistakes or redundancy. The most idiomatic solution is to put this cleanup code in the destructor.

Edit: If you have to support a non-RAII class with an arbitrary cleanup method, then you can make a scoped holder that will call the method for you. It proved more fiddly than I expected, here's the best I could come up with:

class ScopedAction {
 public:
   virtual ~ScopedAction() = 0;
};

ScopedAction::~ScopedAction() {
}

template<class T, class Arg1, void (T::* p)(Arg1)>
class SpecialisedScopedAction : public ScopedAction {
 public:
   SpecialisedScopedAction(T &target, Arg1 &arg) : target_(target), arg_(arg) { }
   ~SpecialisedScopedAction() {
      try {
         (target_.*p)(arg_);
      }
      catch (...)
         {}
   }

 private:
   T &target_;
   Arg1 &arg_;
};

class ScopedActionHolder {
 public:
   ScopedActionHolder(ScopedAction * action) : action_(action) {}
   ~ScopedActionHolder() {
      delete action_;
   }

 private:
   ScopedAction * action_;
};

template<class T, class Arg1, void (T::* fn)(Arg1)>
ScopedAction * makeScopedAction(T & t, Arg1 & arg) {
   return new SpecialisedScopedAction<T, Arg1, fn>(t, arg);
}

Using the following non-RAII class:

enum TransactionState {
   COMMIT,
   ROLLBACK
};

class DBConnection {
 public:
   void finish(TransactionState state) {
      if (state == COMMIT) {
         cout << "committing transaction" << endl;
      } else {
         cout << "rolling back transaction" << endl;
      }
   }
};

You can make a scoped object that calls the finish method, binding in a parameter from local scope like this:

void test() {
   DBConnection conn;
   TransactionState tstate = ROLLBACK;

   ScopedActionHolder cleanup(makeScopedAction<DBConnection, TransactionState, &DBConnection::finish>(conn, tstate));

   cout << "Doing something" << endl;
   tstate = COMMIT;
}

Having to specify the types as template arguments even though they're implicit in the member function pointer type is annoying, I was hoping to work around this but I've run out of time. Also, the syntax is non-obvious and it'll need a new overload of the makeScopedAction function for every different number of method parameters that needs to be supported.

Come to think about it, std::functional or boost::lambda may both be of some help here.

\$\endgroup\$
3
  • \$\begingroup\$ Interesting. This would benefit greatly from variadic templates, but of course that would defeat the purpose of a non-C++0x solution. It's also an interesting complement to the finally I proposed, since a lambda may be punctuation overkill in certain situations. \$\endgroup\$
    – Jon Purdy
    Commented Jun 8, 2011 at 22:31
  • \$\begingroup\$ Yes, lack of variadic templates kills a lot of my neat ideas for templates, such as in my answer to this question. I never got round to learning C++0x, maybe I should take a look. \$\endgroup\$
    – Tim Martin
    Commented Jun 9, 2011 at 13:13
  • \$\begingroup\$ While considering the fault case you now forgot the normal case where the destructor is called when going out of scope without an exception. In this case you would want exceptions to propagate. I think it would be better to not catch exceptions in the wrapper and let the cleanup function handle exceptions or be aware that leaking an exception can lead to termination. (std::uncaught_exception might come in handy here) \$\endgroup\$ Commented Apr 10, 2014 at 8:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.