There are two schools of thought about how to handle errors in computer programs. By "errors" here I mean things that are out of the programmer's control, like "out of memory" or "file not found" rather than mistakes in the actual program (which is a whole other topic). Ideally we want programs which don't just crash when they hit such unexpected circumstances but rather tell the user exactly what the problem is and give them the opportunity to fix it and try again or do something different instead.
The first school of thought is that functions which fail should return some kind of "status code" (which may be "success" or one of several other values indicating different types of error). This is how most of the functions comprising the Windows API signal failure.
The problems with status codes are:
- Whenever you call a function you need to check it's status code or risk missing an error (which usually makes things worse). It's tedious and error prone (mistake prone) to write the code to do this and sometimes programmers forget.
- The information about an error you can return is quite limited - you can say "file not found" but you can't say which file wasn't found or what the program was trying to do at the time (you have to determine this from context).
- If you're using the return value of your function for a status code you can't use it for something more natural. For example, if you're writing a square root function that needs to return an error code when given a input that is negative, it can't also return the answer in a natural way.
The second school of thought is that failures should be indicated by "throwing an exception", which is technically rather more complicated. This involves looking at the function that called the failing function, and the function that called that one and so up the stack until you find a function that says "hey, I know about that exception - pass control to me and I'll deal with it". This eliminates the need for all the functions in between to know about the possible failure at all (other than cleaning up their resources).
Exceptions are new and spiffy and are generally considered to be the "right way" of doing things in new programs. Unfortunately, exceptions also have drawbacks:
- Your program is less "explicit" about what it actually does - certain controls paths are "hidden away" in the exception mechanism. Proponents of exceptions argue that this is a good thing.
- Certain programming styles cannot be used in the presence for exceptions. For example "do X, then do Y, then do Z, then do W if Y failed." Proponents of exceptions argue that these styles are mistake prone (it's all to easy to forget to do Z) and should be avoided anyway.
- While many programming languages in widespread use support exceptions, not everything does. In particular, certain "glue" for interfacing between different bits of code written in different languages generally doesn't support exceptions because they need to be compatible with languages which don't support exceptions (the old "lowest common denominator" problem).
Another criticism often made of exceptions is that they are slow. This may have been true in the past, but in practice it turns out that exceptions are no slower than status codes in the success case (they are slower in the error case, but that doesn't matter too much because errors should be pretty rare).
I think that it is possible to make programs that use exceptions even faster than programs that use status codes. With the right set of tables, the error case code can be completely separated from the normal case code. No code that deals with or tests for errors even needs to be loaded into memory until an exception is actually thrown. Then control passes to a "throw" function which loads the tables and error handlers into from disk and examines the stack to determine which functions the exception passes through and which function will actually handle the exception. The code is faster than the "status code" version because it doesn't have to have all those "if failed" tests sprinkled throughout.
I haven't tried implementing it, but as far as I can tell there are very few places where this scheme would have any overhead at all. One is this situation - State0::RunState() and State1::RunState() can't be folded together with my scheme because they have different stack unwinding information.
The other limitation is that the stack layout and set of constructed objects has to be completely determined by the instruction pointer in any given function. This is usually the case but does mean that functions can't allocate dynamically sized arrays on the stack. These don't tend to be used in practice because they have the serious problem that it is very easy to overflow the stack. If you do know in advance a maximum possible size for this array you may as well just allocate the maximum size.