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:
Post a Comment