Juce and Exception Handling?


#1

I am still quite new to JUCE and C++.

 

At the moment I am considering adding exception handling to my code.

But I saw, that exception handling is rarely used in JUCE.

Does that mean, I should not use it either?

What's your view on dealing with execptions ?


#2

This comes down to opinion mainly, so the short answer would be to do whatever makes sense.

One thing I think most would agree on (within the realms of C++), is that if you use exceptions, use them to express exceptional circumstances (pun intended). So for example, if you have a critical configuration file for which it doesn't make sense to generate defaults, and that file is missing, then you might want to throw an exception with a meaningful error message so as to help you debug the application later.

On the other hand, I don't think it'd make sense to throw an exception just because the you can't find the previous session to reload, since this could happen for a number of reasons.

The point here is; it's subjective.

Disclaimer: This is my personal opinion and not the opinion of or representative of ROLI, JUCE or anything else you can think of.


#3

If I've understood correctly, Juce has no way to propagate exceptions outside of a function that is called by the GUI event loop(*), so all your exception handling would need to be inside whatever functions may throw an exception (or calls code that may throw an exception). This doesn't end up in very nice code. If you forget a try-catch block in any such function, the application is going to unceremoniously be aborted anyway despite your initial plan to be "cool" and deal with exceptions.

If it was possible to have a central location where to handle exceptions, things would be a bit better. This is allowed for example by the Qt framework by subclassing their QCoreApplication class and implementing a virtual function into which exceptions are directed to. Of course that pattern assumes the exceptions are not too exceptional and it will be enough to display the exception that happened or log it to a file or so.

I've personally determined exceptions to be more trouble than useful. I still pretty religiously use the RAII pattern to manage object life times, though. IMHO it's stupid RAII is almost always mentioned as a technique that's useful when exceptions can happen, when it's actually just as useful when exceptions are not expected to happen.

(*) Perhaps this was only when doing GUIs for plugin kinds of things, but since I want my code to run both as stand alone apps and plugins, the situation for me practically is that Juce doesn't have support for exceptions of its own.


#4

Thanks for your views.

I'll have to think about it.


#5

I don’t think that whether JUCE uses exceptions necessarily influences whether you do or not. You can still convert JUCE returns codes into exceptions where you want to. I think most libraries don’t use exceptions for various reasons, one of which is that they don’t want to force them on users who don’t want them.

The subject of exceptions vs. return codes produces lots of discussion. Personally I prefer exceptions. I think one problem people run into early is that once they decide to start trying them, the first question they ask is, “In which cases should I throw an exception?” And the answer people often give is to “only throw an exception in exceptional circumstances” which is a totally unhelpful piece of advice because you’re then left with the (same) problem of figuring out what the definition of “exceptional circumstance” is. Besides the fact that I think that’s wrong anyway.

I usually stick to these rules when using exceptions, which is my personal opinion:

A function has a job to do which you should describe clearly. If it cannot do that job successfully, throw an exception.

The reason for this is simple. If a function was unable to do its job successfully, then the program may be in an unknown state. If execution is allowed to continue, there may be no telling what damage can be done, for example the wrong file getting deleted. So the program should not be allowed to go further.

Obviously, defining the job of a function is important because that in turn tells you when you should throw an exception. This can be tough sometimes. For example, say that you write a function to allow callers to retrieve a value from a dictionary. If the value is not found—that is, it has not done the job it’s meant to do—should you throw an exception? If you do, then you’re expecting callers to guarantee that the value exists before they try to retrieve it. You’d require one lookup to ensure it exists and another to actually retrieve it, and that’s not good performance. It may be a common occurrence that the value is not in the dictionary and you don’t want to throw each time. Perhaps an interesting example of dealing with this comes from the MS .NET framework. The Dictionary class includes both the Item property and the TryGetValue function. They both retrieve a value from the Dictionary but the Item property throws an exception and TryGetValue does not. The job of each is defined differently and the name “TryGetValue” reflects that difference. I’m not saying that you should have duplicate functions everywhere in your code. I’m just using this as an example of how the way you define the job of your function determines whether it throws an exception or not.

Keep in mind contract programming. If the function is internal to the program, then don’t throw an exception on bad parameters. The parameters are assumed to be valid by the contract. If they are not, then you have a design problem that you can fix, not a runtime problem. So just use assertions to check parameters. However, if the function is part of a public API, then also throw an exception on bad parameters because the caller may get them wrong. (Note that the MS .NET API throws on bad parameters.)

Write exception-safe code. One good resource for this is Herb Sutter, perhaps check his book Exceptional C++ (which is not just about exceptions despite the title.)

Don’t catch and handle exceptions at every level. Only catch exceptions in a middle level when you can correct the error and continue on. Otherwise, let them propagate up. Do catch exceptions at the highest level to report the error to the user.

Note that your program should not typically be throwing exceptions in the normal case when everything runs correctly. So the performance cost of a thrown exception is irrelevant. If something goes wrong and an exception is thrown, then in that case it doesn’t matter how long it takes.


#6

Thanks - some very good advice there!

Yes, like a lot of libraries, we avoid exceptions in JUCE because many users don't want them. And I'm not a big fan of them myself!

There are a couple of places where we do use exceptions internally though, in places where they make the code structure much simpler (e.g. handing errors inside the Javascript parser). Another concern for us is that at some point we may need to make the library compatible with compilers or platforms where exceptions aren't available, and we don't want to be stuck with lots of code to change in that case.

(I should mention that all the code in JUCE is exception-safe, of course)


#7

Thanks for the clarification, that all JUCE code is exception safe.

Do you mind me asking: Why are you not a fan of exceptions?


#8

It looks like the "central exception handling" is possible to achieve in a Juce stand alone application. (By overriding JUCEApplication::unhandledException). I didn't remember about that exactly when I wrote my post above. However, it does look like that mechanism is not available to be used when there is no JUCEApplication present. (GUIs for things like VST plugins.) Or have I missed something?


#9

[quote]I’ve personally determined exceptions to be more trouble than useful. I still pretty religiously use the RAII pattern to manage object life times, though. IMHO it’s stupid RAII is almost always mentioned as a technique that’s useful when exceptions can happen, when it’s actually just as useful when exceptions are not expected to happen.[/quote]True, you can manage object lifetimes without exceptions. I think the problem you run into though is that constructors cannot have return values. A common pattern people use is to have some function called “bool Init()” or “bool Initialize()”. After constructing an object, you’re expected to call Initialize(). The object cannot be used—that is, none of the other functions are supposed to be called—until you first call Initialize() successfully. The constructor itself may be empty and do nothing, or at least it does nothing that can fail. The problem with this is that after you construct an object you have an object that is not yet in a usable state and you depend on the caller to call Initialize() to get it into a usable state.

Resource Acquisition Is Initialization means that if you can construct an object successfully at all then it is in a usable state. That is, acquiring an object initializes it at the same time. So an object that exists is always in a usable state. For this, you need to have exceptions because constructors don’t have return values. (However, I imagine you can avoid this by using factory functions to create objects. It’s just more awkward and not part of the language.)

Regarding threads, I know what you mean about threads and exceptions. But :slight_smile: the new standards now have a mechanism to propagate exceptions across threads.


#10

It's nice that you put in the effort to be exception safe when the library doesn't even use exceptions itself. :-) There are a couple different levels of exception safety. I assume you mean that JUCE doesn't leak resources (basic guarantee).

You got me to thinking about what I wrote earlier, which I should amend. I said that an application can use JUCE and also use exceptions, but I was thinking of the application as sitting on top of JUCE and not considering virtual functions. If I override a JUCE virtual function, I wouldn't want to throw an exception from that function of course. JUCE is expecting to see return values there.