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.