Error handling is a tricky beast in the best of times. It pretty much boils down to how big a deal the error is, and what if anything is expected to happen with it when it occurs.
There are four basic paths you can follow:
- Throw an exception
- The sledgehammer of error handling. A great tool, definitely want to use it if you need it, but if you're not careful you'll end up smashing yourself in the foot.
- Essentially skips everything between the
throw
and the catch
, leaving nothing but death and destruction in it's wake.
- If it's not caught, it will abort your program.
- Return a value that indicates failure
- Leave it to the programmer to check for success and react accordingly.
- Failure value would depend on type. Pointers can return
NULL
or 0
, STL containers return object.end()
, else otherwise unused values can be used (such as -1
or ""
).
- Process the condition gracefully
- Sometimes, an error isn't really an error, just an inconvenience.
- If useful results can still be provided, a mistake can easily be swept under the carpet without hurting anyone.
- For example, an out of range error can just return the last variable in an array, without needing to resort to any of that messy exception stuff.
- So long as it's predictable and defined, the programmer can make of it what they wish.
- Undefined behaviour
- Hey, programmers shouldn't be giving you bad input in the first place. Let them suffer.
In general, I would resort to option one only for stuff that's program-breaking, for things that I don't really expect to recover from without concerted effort. Otherwise, using exceptions as a form of flow control is little better than going back to the days of goto
.
Option two is probably the most common for non-program-breaking errors, but it's effectiveness really depends on the types of return you're dealing with. It is advantageous since it lets the programmer control the flow locally by detecting failures and recovering themselves. When dealing with overloading operators, it is of limited use, but I figured I'd throw it in for the sake of completeness.
Option three is very circumstance-specific. Many errors can't be handled in such a way, and even the ones that can can lead to unintuitive results. Use with caution, and be sure to document thoroughly. Or, don't document it at all, and pretend it's option four.
Now, as to the specific example provided, that being an out of range error on an overloaded operator[]
, I would personally go for option four. Not because I particularly enjoy watching other programmers suffer when they deal with my code (I do, incidentally, but that's tangential to the discussion), but because it's expected.
Most cases where a programmer would be using operator[]
, they expect to handle their own bounds checking and don't rely on the type or class to do anything for them. Even in the STL containers, you can see operator[]
(no range checking) in parallel with the otherwise redundant object.at()
(which does range checking). Reflecting the expected behaviour with your own overloaded operators tends to make for more intuitive code.