7

I would like your help in understanding what are the possible approaches to using/disabling exceptions in C++.

My question is not about what is the best choice but just about what are the possible options and what these options imply.

Currently, the options I can think of are:

  1. Compiling with -fno-exceptions and giving up most std containers (possibly defining internal containers which do not throw, such as suggested in SpiderMonkey Coding_Style)
  2. Just avoiding to throw and catch in own code, but still using std containers which may throw exceptions. Being happy with the fact that, in the case of exceptions, the program may terminate without stack unwinding, and that even RAII handled external resources may be left hanging. (This seems to be Google C++ approach according to answers to this SO question)
  3. No using exceptions but wrapping all in a catch all std::exception try block just to make sure stack is unwound and RAII handles to external resources are released before program is terminated, as for this Cert C++ rule
  4. As above, but also throwing exceptions which will ultimately result in program termination.
  5. Also using catched exceptions and recovering from exceptions.

I would like to know if my understanding of options is correct, and what I might be missing out or understanding wrong.

I would also like to know whether constraints on granting basic exception safety make sense for options 2-4 (where exceptions always ultimately lead to program termination) or if/how exception safety requirement can be relaxed/restricted to specific cases (e.g. handling external resources, files).

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
Gianni
  • 458
  • 5
  • 17
  • Generally the _reason_ for disabling exceptions is that they introduce non-zero overhead and have unpredictable run-time space/time cost which is unacceptable in many situations. These issues appear as soon as _anything_ in your code throws an exception, so I don't see how options 3 and 4 really make sense at all. That being said, this question _is_ too broad and I'd vote to close if the bounty didn't stop me. – You Feb 22 '19 at 10:16
  • if actual overhead and space/time cost is paid as soon as anything throws, but we are anyways just trying to safely terminate program whenever anything throws, overhead and space/time cost constraints do not seem too relevant. Options 3 and 4 are there just to make sure stack unwinding takes place and external resources which would be released by RAII destructors are not left hanging. Does this make no sense at all? – Gianni Feb 22 '19 at 10:39
  • 2
    Well, if you're already paying the cost of exceptions, you might as well go with option 5 and actually do error handling where appropriate, is my point. Whether you do that once at the top level (options 3/4) or where it's actually sensible (option 5) is not a "regulating use of exceptions" issue, it's a general error handling strategy issue. – You Feb 22 '19 at 11:19
  • Thanks, i tried to make my title cleared. It seems like the real cost of exceptions is paid in the moment I catch an exception. So am I really paying the time/memory cost of exceptions in options 3/4, where exceptions are never caught but for program termination? And this gets back to my final question: option 5 imposes me to code with exception safety in mind. All my code must guarantee at least basic exception safety. Is this really needed if I go for option 3? – Gianni Feb 22 '19 at 12:39
  • 1
    Note that -fno-exceptions does not just disable exception support, it also makes it impossible for -fexceptions code linking the previously built code from properly passing exceptions _over_ code in that previously build code (i.e. from a callback passed into a library function). – rubenvb Feb 28 '19 at 13:01
  • http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1640r1.html – Chef Gladiator Feb 07 '20 at 10:40

1 Answers1

3

Update

Strictly speaking, when you want to disable exceptions only compiling with no exception support is true way to go, so Option 1. Because when you want to disable exceptions you should also not use or even handle them. Raising an exception would terminate or go to a hard faulty on most implementations instantly, and only by this you would avoid overhead implications like space or performance overhead even how small (see below) they are.

However if you just want to know which paradigms about exception usage are out there, your options are almost right, with the general idea you haven't mentioned of keeping exceptions exceptional and doing things that are likely or even just could throw, at program startup.

And even more in general, in comes to error handling in general: Exceptions are there to handle errors which you come across at runtime and if done correctly errors you can ONLY detect at runtime. You can avoid exceptions by simply making sure to resolve all problems you can detect before runtime, like proper coding and review (code time), using strict types and checks (templates , compile time), and rechecking them again (static analyser).

Update 2

If you understand you wrong about caring about exception safety I would say: Basically at first it depends whenever you enable exceptions in general: If they are disabled you can't and shouldn't care about exception safety since there are none (or if they try to come into existence, you will terminate/crash/hardfault anyway). If you enable exceptions but don't use them in your code, like in case 2, 4 and 3, no problem you want to terminate anyway, so missing cleanup code is not relevant (and the stuff in 3. gets still run in any case). But then should make it clear to everybody that you don't want to use them, so they won't try to recover from exceptions. And if you use them in your code, you should care about exception safety in the way, that when an exception gets thrown, you clean up after your self too, and then its up the main handler or future code changes, when ever you still terminate or can recover. Not cleaning up but still using exception wouldn't make sense. Then you can just stick to option 1.

This should be to my knowledge exhaustive. For more information see below.

Recommendation

I recommend option 4. and I tell you why:

Exceptions are a very misunderstood and misused pattern. They are misused for program flow like the languages Java does in a excessive. They are often forbidden in embedded or safety code because of their nature that is hard to tell which handler will catch it, if there is one, and how long this will take, for which the C++ std just says basically "implementation magic".

Background

However in my reasoning the hate for exceptions is basically a big XY problem: When people are complaining that their recovery is broken and hard to tell, then the usual problem is that the don't see you can't or should do much about the most exceptions, that's what they are for. However things like a timeout or a closed tcp connection are hardly non normal, but many people use exceptions for that, that would be wrong of course. But if your whole OS tells you that there is no network adapter or no more memory what could you do? The only thing you probably want is trying to log the reason somewhere and this should be done in one try/catch around the main block.

The same is for safety/real-time programs: For the example of running out of memory, when that happens you are **** anyway, the strategy then is to do this stuff at an unsafe initialisation time, in which exceptions are no problem too.

When using containers with throwing members its a similar scenario, what would you be able to do when you would get their error code instead? Not much, thats the reason why you would make sure at code time that there is no reason for errors, like making sure that an element is really in there, or reserving capacity for your vectors.

This has the nice benefit of cleaner code, not forgetting to check for errors and no performance penalty, at least using the common C++ implementations like gcc etc.

The reason of 3. is debatable, you could do this in my opinion too, but then my question is: What do you have to do in the destructors which wouldn't cleanup your operating system anyway? The OS would cleanup memory, sockets etc.. If have a freestanding scenario, the question remains, whenever different: You plan to halt because your UART broke for example, what would you like to do before halting? And why can't you do it in the catch block before rethrow?

To sum up

  1. Has the problem of not using any throwing code, and still be left with the problem of how to handle rare error codes. (Thats why so many C programmers still use goto or long jumps)

  2. Not viable IMHO, worst of both

  3. as mentioned ok, but what do you need to do in your static DTors, what you even un-normal termination wouldn't do?

  4. My favourite

  5. Only if you really have rare conditions that you are actual able to recover from

What I mean as a general advice: Raising and exception should mean that something happened that should never happen in normal operation, but happened due to a unforeseeable fault, like hardware fault, detached cables, non found shared libraries, a programming fault you have done, like trying to .at() at an index not in the container, which you could have known of, but the container can not. Then it is only logical that throw an exception should almost every time lead to program termination/hard fault

As a consequence of compiling with exception support, is that for example for a freestanding ARM program, your program size will increase by 27kB, probably none for hosted (Linux/Windows etc.), and 2kB RAM for the freestanding case (see C++ exception handler on gnu arm cortex m4 with freertos )

However you are paying no performance penalty for using exceptions when using common compilers like clang or gcc, when you code runs normal i.e. when your branches/ifs which would trow an exception, are not triggered.

As a reference i.e. proof to my statements I refer to ISO/IEC TR 18015:2006 Technical Report on C++ Performance, with this little excerpt from 7.2.2.3:

Enable exception handling for real-time critical programs only if exceptions are actually used. A complete analysis must always include the throwing of an exception, and this analysis will always be implementation dependent. On the other hand, the requirement to act within a deterministic time might loosen in the case of an exception (e.g. there is no need to handle any more input from a device when a connection has broken down). An overview of alternatives for exception handling is given in §5.4. But as shown there, all options have their run-time costs, and throwing exceptions might still be the best way to deal with exceptional cases. As long as no exceptions are thrown a long way (i.e. there are only a few nested function calls between the throw-expression and the handler), it might even reduce run-time costs.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • _My question is not about what is the best choice but just about what are the possible options and what these options imply._ but +1 for the details and references! _What do you have to do in the destructors which wouldn't cleanup your operating system anyway?_ this is actually want I wanted to know, is there something that the OS would not clean up by itself? – Gianni Feb 28 '19 at 12:37
  • You mean possible options to minimise every exception handling? And by implications which overhead they have? – Superlokkus Feb 28 '19 at 12:40
  • I wanted to know if my list of 5 possible exception handling strategies is complete, if some other strategy missing or if some should be removed. By implications i mean for example if I should care about granting basic exception safety when I go for strategy 2-4, or what do I risk going for strategy 2 (you partially answered on this). – Gianni Feb 28 '19 at 12:46
  • How about now @Gianni – Superlokkus Feb 28 '19 at 13:05
  • 1
    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1640r1.html – Chef Gladiator Feb 07 '20 at 10:40