korona wrote:Yakk wrote:The ideal situation I'd want is a compile-time enforced "you must handle the set of answers that this function can return". But that is rare outside of some relatively esoteric languages (like Haskell).
I guess that is the idea behind Java's checked exceptions and I don't think that this design decision was that great.
How so?
What is so bad about C++ exceptions (when used properly)?
Because on any reasonably large code base, I can't think of a way they can be used properly, barring a really really huge amount of code discipline and static checking utilities aimed at making sure things aren't going wrong.
As written in C++, each throw() is an arbitrary depth goto into your calling stack. Each call to a function without a catch(...) is also an arbitrary depth goto into your own stack. There is no way you can locally determine the flow of execution without wrapping each external bit of code in a try{}catch(){} block.
You can fix this by rigid code standards that basically come down to "do not use exceptions", or "only use exceptions in functions that are not exported outside of your translation unit, and decorate the name of such exception throwing functions, only throwing one kind of exception", etc.
Of course you cannot solve every problem with them and there are some gotchas but in many situations they are a good choice for handling unexpected errors (e.g. out of memory, ...).
Out of memory is an interesting problem.
Most C++ code bases of any reasonable size do not deal with running out of memory well. Regardless of the exception being thrown or not. The best they can usually do is go into emergency shutdown mode, often.
Some small code bases can isolate their memory allocation to a handful of areas. And handle running out of memory in those areas. Note, however, that a nullptr return from a new call would be about as useful.
Of course there are better ways to handle multiple return values.
I'm talking both about multiple return values and multiple return
paths. Exceptions produce both, but there is a serious lack of the ability to in-language describe the return paths that a given function can generate.
Being able to understand what code does by reading it is an important feature of a language, and exceptions mean that there are code execution paths that are not at all clear when reading things locally. You could take it as a given that every function call will throw an exception, and do unit testing based on this assumption, and see if things work...
But I've yet to see a large C++ code base that does that.
To be clear, I'm not all that keen with the verbosity of Java exceptions. I'd want meta-decoration coding in which the "brutal exceptions" are implicitly added to a function by the "standard" function decoration modifier.
Ie, suppose that you "by default" decorated every function with the modifier "function". That modifier implies that you can throw division by zero, memory shortage, segmentation fault, and a myriad of other critical exceptions. The programmer could write their own standard modifier that presumes a different set of possible exceptions.
Even better, when calling an incompatible function, your modifier might describe what happens. Maybe you have an unhandled exception exception and your decorator says that you implicitly wrap all calls to more broadly throwing functions in a try-catch block that rewraps their exception into an unhandled exception, then rethrows the unhandled exception. Such decoration could be on the level of a function, or on a block, which lets you write code that doesn't care about exceptions, or more careful code that does.