debugcppMinor
An implementation of "finally" in C++0x
Viewed 0 times
implementationfinallystackoverflow
Problem
I've made a quick
(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:
Regardless of whether
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
Anyway, assuming someone does find it useful, is there anything important that I missed?
finally type in C++:template
class finally_type {
public:
explicit finally_type(F f) : function(f) {}
~finally_type() { try { function(); } catch (...) {} }
private:
F function;
};
template
finally_type finally(F f) { return finally_type(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?
Solution
Bear in mind that code inside the function called by
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:
Using the following non-RAII class:
You can make a scoped object that calls the
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
Come to think about it,
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 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
ScopedAction * makeScopedAction(T & t, Arg1 & arg) {
return new SpecialisedScopedAction(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(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.Code Snippets
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);
}enum TransactionState {
COMMIT,
ROLLBACK
};
class DBConnection {
public:
void finish(TransactionState state) {
if (state == COMMIT) {
cout << "committing transaction" << endl;
} else {
cout << "rolling back transaction" << endl;
}
}
};void test() {
DBConnection conn;
TransactionState tstate = ROLLBACK;
ScopedActionHolder cleanup(makeScopedAction<DBConnection, TransactionState, &DBConnection::finish>(conn, tstate));
cout << "Doing something" << endl;
tstate = COMMIT;
}Context
StackExchange Code Review Q#2864, answer score: 3
Revisions (0)
No revisions yet.