[Edit]
TL;DR: Neither the exception mechanism or returning error codes recovers from erroneous state; neither approach changes where the recovery code needs to live in order to successfully handle a problem. The difference is that the C++ language can transparently propagate an exception to the recovery code which, yes, will be much the same for both types.
[/Edit]
OKay, let's say an I/O library has failed to open a file named "LastResortConfig.dat", and your code has told it to throw an exception on an error. What exception should it throw?
O_o
So what does that code do?
Two people can play the "Post Nonsense at CBoard" game.
Let's say library functions fails, your code has requested an error value to be returned.
So what does your function do with an error code? Let's look at the three choices:
1): Ignore the error. Ignoring the error avoids the need for the application to recover. Wait. If we don't respond to an error, why the hell are we allowed near a compiler?
2): Translate the error. Translating the error allows us to be more specific about the error than the library we are using. Of course, we haven't actually responded to the error; we are expecting calling functions to respond to the error code we return. So what does the calling functions do with an error code? Yay. I love recursion!
3): We don't know what to do with the error code so we just `return' whatever we got from the library. Responding gracefully in the face of failure is "Someone Else's Problem". If I wanted to solve other people's problems, I'd be a marriage counselor.
That's a big price to pay so that your code which requests some file operations can be written cleanly.
Writing code that takes errors seriously is a lot of work regardless of how you report the context. If you are going to gracefully handle the failures of a primitive, you have to do the work. If you aren't going to gracefully handle the failures of a primitive, why are we even talking? You don't need error code or exceptions; you can just let the application crash.
Let's take a serious look at the cost of an exception throwing agent using good C++ idioms.
Code:
FileReader s("filename.ext", MyAgent);
Done. We've just added an extra argument to the constructor.
Is that a big price to pay? Let's look at the `MyAgent' functions just in case I've hidden a burden.
Code:
void MyAgent
(
int fError
)
{ switch(fError)
{
case /* ... */: {
throw Whatever1(/* ... */);
} break;
case /* ... */: {
throw Whatever2(/* ... */);
} break;
case /* ... */: {
throw Whatever3(/* ... */);
} break;
}
}
We've translated an error code into an exception. Wait.
So, if you want the IO library to throw exceptions relevant to the application (rather than itself) there is a detailed setup required for every operation.
Well. We now know for a fact your "detailed setup required for every operation" isn't remotely true.
The basic concern I see with your approach is that it doesn't scale.
A bit of code that internally translates error codes into exceptions doesn't scale? Are you sure?
Code:
FileReader sIn("filename.ext", MyAgent);
int sId;
string sName;
sIn >> sId >> sName;
Looks fine to me, but we want to be fair so let us take a look at responding to the returned error code.
Code:
FileReader sIn("filename.ext");
int sError;
int sId;
string sName;
sError = (sIn >> sId);
if(sError)
{
MyAgent(sError);
}
sError = (sIn >> sName);
if(sError)
{
MyAgent(sError);
}
Wait! The agent doesn't have to raise an exception. We can allow `MyAgent' to do nothing if the error code is clean.
Code:
FileReader sIn("filename.ext");
int sId;
string sName;
MyAgent(sIn >> sId);
MyAgent(sIn >> sName);
Wait. We have operator overloading! The `MyAgent' function raises an exception to report an error so we can use the return value to facilitate chaining!
Code:
FileReader sIn("filename.ext");
int sError;
int sId;
string sName;
MyAgent(MyAgent(sIn >> sId) >> sName);
Wait! Now we've just applied an algorithm to the result an operation while returning the same in the case of successful execution. We might need to respond with different exceptions at some later date, and we don't like to repeat ourselves. Let's factor the separate goals so we can compose the functions from whatever agent happens to be useful to the client.
Code:
FileReader sIn("filename.ext");
int sError;
int sId;
string sName;
Chain(Chain(sIn >> sId, MyAgent) >> sName, MyAgent);
I am so silly. I keep forgetting that we are talking about the C++ language. The C++ language has classes an operator overloading. We can just use those tools.
Code:
struct MyFileReader
{
// ...
template
<
typename F
>
MyFileReader
(
const char * fName
, F f
):
m(fName)
{
}
// ...
FileReader m;
};
// ...
MyFileReader sIn("filename.ext", MyAgent);
int sError;
int sId;
string sName;
sIn >> sId >> sName;
Sweet. Now we have the boilerplate response to error conditions isolated behind an interface so our client code is clean.
o_O
Code:
sIn >> sId >> sName;
Code:
sIn >> sId >> sName;
$%*@
*shrug*
Well, we still have the issue of how to respond to the exceptions.
^_^
I love recursion!
Soma