While having dinner with some colleagues. We had an interesting discussion on the usefulness of throwing exceptions from libraries.
The discussion lead to comments on two books:
- C++ Coding Standards: 101 Rules, Guidelines, and best practices. By Sutter and Alexandrescu.
- API Design for C++. By Martin Reddy.
As I remember the conversation the statement was more or less "A (well designed) API should not throw". My first thougt was that some time had passed since I read both books and that I did not remember the rule to be so severe. So I decided to go back and read again the books. First of all let me say that I enjoyed a lot reading both of them. C++ Coding Standards is an excellent book and I am really willing to see when Sutter and Alexandrescu release a C++11 update of the book. C++ API Design is a good book. Although I may have some concerns on specific parts of it, I like it. And I have used some chapters in an invited course I gave on software design and patterns in C++.
Now, let's go to the topic.
A lot has been said about exceptions in general as an error reporting mechanism. One of my colleagues found conflicting the following advices/rules:
- Prefer exceptions to report errors. This is guideline 72 from Sutter and Alexandrescu.
- Don't allow exceptions to propagate accross module boundaries. This is guideline 62 from Sutter and Alexandrescu.
At first sight, it seems that both rules are conflicting. Are they? As some imprecise reference on API design for C++ was also done. I decided to go first to this one. I did not really find anything in Reddy's book against the usage of exceptions in general. What Reddy does is to provide good suggestions if you provide error reporting through exceptions in your library. To be fair, it also states that "Google forbids the use of exceptions in their C++ coding conventios because most of their existing code is not tolerant of exceptions". However that does not seem an argument against the usage of exceptions.
Now, let's go back to rules 62 and 72 from the C++ Coding Standards book. I will start with rule 72. I usually prefer this rule and I usually report errors through exception. The key rationale is that it easily allows to make a separation between the point were the error condition is detected and the point where the error condition is handled. This is generally what happens when a library does not know what to do when it detects the error.
Exceptions exhibit advantages over returning error codes:
- Exceptions can't be silently ignored.
- Exceptions propagate automatically.
- Exceptions handling removes error handling and recovery from the main line of control flow.
- Exception handling is a very good alternative for reporting errors from constructors and operators.
Some general disadvantages may be identified:
- Writing exception safe code is not easy for beginners and special care must be taken in destructors.
- Having exception compiler flags activated may lead to larger executable sizes. And in some platforms to minor performance penalties. However as even the standard library may throw exceptions it does not seem practical to deactivate those compiler options. And if the flags are activated the performance penalty is very low or even zero.
And finally let's got to the conflictive rule 62. If I read it as an absolute rule I am told that I should not propagate exceptions outside a module. My first answer was: "Well it depends on what you call a module". So I decided to read again rule 62.
I found out that Sutter and Alexandrescu name several things as a module. Their general definition is any cohesive unit of release maintained by the same person/team and consistently compiled with the same compiler and settings. Several grain size apply:
- A single object file delivering a single class.
- A single shared/dynamic library
As I read rule 62 and go through the summary I find an "unless". So the rule really says:
"Don't allow exceptions to propagate accross module boundaries unless you control the compiler and compiler flags used to build both sides".
Now I got it. The reasoning behind the rule is that there is no standard for ABI. That is Application Binary Interface is dependent on compiler vendors and platforms.
So we have the following cases:
- Applications using static linking. In that case you do not need to bother you can throw exceptions as they are needed.
- Applications using dynamic linking in which the dynamic library and the client code are compiled by the same team, or tat least use the same compiler and the same compiler flags. I do not see a problem here either.
- Applications using dynamic linking in which the dynamic library are distributed in binary format and/or are compiled with different settings. That is the case where we cannot have the luxury of propagating exceptions.
Is there any other reason to avoid the usage of exceptions? Well. Yes. There are.
Some specific guidelines for high integrity safety critical software may exclude using exceptions for different reasons. But that's a diffeent story.