Friday, March 28, 2014

A simple alternative to C++ exceptions

I’ve been doing a lot of C++11 research and coding ultimately, and I’m still upset with the C++ exceptions. When reading the concepts, it seems to be a reasonably good alternative to simply return a boolean, but as you try to stuff it in your code, you begin to feel like manipulating a stressed hedgehog. And it’s very frustrating, since you have to stop thinking about your code – what really matters –, to think about the implications of the code you are writing upon the code itself.

Usually, my needs are really simple, I just need an extended way to report a success or a failure from a function. I never wrote something that really needed to tackle differents kinds of errors: it’s just success, or failure with a reason, which usually popped in an alert message box.

After quitting the exceptions, when trying to solve this problem, my first move was to use an extra string pointer as the last parameter to hold eventual error messages. It goes like this:
bool Func(int p1, int p2, wstring *pErr=nullptr) {
	if(false) {
		if(pErr) *pErr = L"Error message.";
		return false;
	}
	if(pErr) *pErr = L""; // all good
	return true;
}

bool Second(float nn, wstring *pErr=nullptr) {
	if(!Func(42, 1337, pErr)) { // will return error message from inner function
		return false;
	} else if(false) {
		if(pErr) *pErr = L"Another error.";
		return false;
	}
	if(pErr) *pErr = L""; // all good
	return true;
}

{
	wstring err;
	if(!Func(42, 1337, &err))
		scream(err.c_str());
}
There is no problem with this approach, and I used it for quite some time. It is clear, and one would have no doubts about what is going on. It is, however a bit cumbersome to write all those pErr checks. So at some point I started considering something else.

My second approach was to use a standard pair as the return type to the function. Something like this:
pair<bool, wstring> Func() {
	if(false)
		return make_pair(false, L"Error description message.");
	return make_pair(true, L"");
}

{
	pair<bool, wstring> ret = Func();
	if(!ret.first)
		scream(ret.second.c_str());
}
I never really used this. It doesn’t look clear enough, and the declaration of the pair variable, just to hold the return value, doesn’t amuse me.

Then I start thinking about writing a “bool on steroids” class, specifically overloading the operator bool, so there would be no need to declare a variable just to hold the result value of the invoked function. This is the class:
#include <string>

class Failed {
public:
	Failed(bool allGood)                   : _hasFailed(!allGood) { }
	explicit Failed(std::wstring reason)   : _hasFailed(true), _reason(reason) { }
	explicit Failed(const wchar_t *reason) : _hasFailed(true), _reason(reason) { }
	virtual ~Failed()                      { }
	operator bool() const         { return _hasFailed; }
	const wchar_t* reason() const { return _reason.c_str(); }
private:
	bool _hasFailed;
	std::wstring _reason;
};
And this is the usage:
Failed Func() {
	if(false)
		return Failed(L"This function failed.");
	return true;
}

Failed Second() {
	if(Failed f = Func()) { // if Func() returned false
		return f; // return from inner function
	} else if(false) {
		return Failed(L"Another error message.");
	}
	return true;
}

{
	if(Failed f = Second())
		scream(f.reason());
}
This approach looks a lot elegant to me. It’s clean, allows the chaining of error messages, and it even allows the inheriting of the Failed class to add more specific error data – although I never really needed this.

The only thing which makes me uncomfortable with this class is the _hasFailed member, which holds a reversed boolean value of the object meaning: if there’s a success, it’s false; if there’s a failure, it’s true. I implemented it this way so that I could use a declaration of the class within an if statement, with no need to declare a variable just to hold the result, as I did on the previous example when I returned a standard pair. Also, it explains why I chose the name “Failed” instead of something like “BigBool”: to explicit the idea of a failure being handled by the if.

Other than that, I consider it a neat approach, and that’s the best solution I could come up with so far.

No comments: