81

Note: I'm not playing the devil's advocate or anything like that here - I'm just genuinely curious since I'm not in this camp myself.

Most types in the standard library have either mutating functions that can throw exceptions (for instance if memory allocation fails) or non-mutating functions that can throw exceptions (for instance out of bounds indexed accessors). In addition to that, many free functions can throw exceptions (for instance operator new and dynamic_cast<T&>).

How do you practically deal with this in the context of "we don't use exceptions"?

  • Are you trying to never call a function that can throw? (I can't see how that'd scale, so I'm very interested to hear how you accomplish this if this is the case)

  • Are you ok with the standard library throwing and you treat "we don't use exceptions" as "we never throw exceptions from our code and we never catch exceptions from other's code"?

  • Are you disabling exception handling altogether via compiler switches? If so, how do the exception-throwing parts of the standard library work?

  • EDIT Your constructors, can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code upon failure (which the constructor can't), or do you do something else?

EDIT Minor clarification 1 week after the inception of the question... Much of the content in comments and questions below focus on the why aspects of exceptions vs "something else". My interest is not in that, but when you choose to do "something else", how do you deal with the standard library parts that do throw exceptions?

Johann Gerell
  • 24,991
  • 10
  • 72
  • 122
  • 5
    You actually can use the standard library without exceptions are used. – πάντα ῥεῖ Jun 08 '16 at 10:49
  • 8
    Not sure why this is voted close. It's a good question. What's the strategy? – Robinson Jun 08 '16 at 10:50
  • @πάνταῥεῖ so what happens if you call `vector::at` with out of range index – M.M Jun 08 '16 at 10:50
  • 18
    People that "don't use exceptions" are usually happy to have their program terminate when, say, there's no memory available. They often work on OSses that never run out of memory anyway. And they would avoid using exceptions on, say, broken promises, for control flow, and consider it a hard error if such an exception were to occur (again justifying termination (of both program and programmer)). – Kerrek SB Jun 08 '16 at 10:50
  • @πάνταῥεῖ - Do you mean if exception handling is disabled at compile time or just "not used"? – Johann Gerell Jun 08 '16 at 10:51
  • 1
    @M.M See [here](http://coliru.stacked-crooked.com/a/441aaaa628619651). – πάντα ῥεῖ Jun 08 '16 at 10:59
  • 9
    A lot of times functions throw exceptions when they are called poorly, for instance bad arguments. Then you write your code such that you make correct calls -- if you haven't managed to write the correct code to call a function, then I don't think you'd write the correct code to handle the resulting exception either. Other exceptions, such as out of memory, are not recoverable, so you can't 'handle' them -- you just let them fly, log them, and crash & burn. – MicroVirus Jun 08 '16 at 11:06
  • 1
    It's also not that you don't handle exceptions, it's more that you are try to avoid throwing new ones yourself. If a (standard) library function uses exceptions, then you go with it, but in functions you write yourself you'll do your best to avoid exceptions, especially for regulating control flow -- exceptions are then only used when something truly exceptional happens. – MicroVirus Jun 08 '16 at 11:10
  • @πάνταῥεῖ that program produces the same output with or without `-fno-exceptions` so it's not very illuminating to OP's question – M.M Jun 08 '16 at 11:27
  • @M.M It was you who asked _what happens_. – πάντα ῥεῖ Jun 08 '16 at 11:31
  • @πάνταῥεῖ yes, as a way of inquiring about what you mean by your first comment. I thought you were saying the standard library can be used without exceptions; however in the example you provided there was an exception thrown. – M.M Jun 08 '16 at 11:32
  • 1
    @M.M I wanted to demonstrate that the function still can be _used_, though [`try` `catch()` isn't possible](http://coliru.stacked-crooked.com/a/81949cf0feebf723). – πάντα ῥεῖ Jun 08 '16 at 11:35
  • Note that many compilers offer 'compile without exceptions' options, but they all implement them differently - unsurprising since as soon as you invoke such an option you're stepping outside the standard anyway. Some simply fail with a syntax error if they encounter a try, catch or throw keyword, while others happily compile such statements but simply don't generate stack unwinding code & tables - effectively producing undefined behaviour if an exception is ever thrown. – Jeremy Jun 08 '16 at 12:05
  • The question is too broad. Examples of concrete problems would lead to a useful analysis, but why should we discuss about a "global policy" in the first place? That should only come after analyzing a sufficiently representative set of concrete examples. – Daniel Daranas Jun 08 '16 at 13:49
  • 5
    @DanielDaranas - and yet all answers and comments so far managed to address this perfectly fine. "One who says it cannot be done should not interrupt one who does it" - Chinese proverb. :-) – Johann Gerell Jun 08 '16 at 14:23
  • 2
    Basically agreeing with Kerrek SB, but more provokingly: The statement "We don't use exceptions" is at least occasionally a disguised way of saying "We are so clever, we don't make errors" - maybe trying to make this statement appear less arrogant, less naive, and like a technically sound decision that is justified with certain quality standards. Which are not met. Hence the crashes. – Marco13 Jun 08 '16 at 14:26
  • @Marco13 - I actually think that reason for claiming "we don't use exceptions" is under-represented. Mostly I see and hear actual reasoning for "not using exceptions", although the reasoning itself is often to some extent flawed due to misconceptions about how the environment actually works; which is why I posed this question in the first place, to hear how _non-flawed_ reasoning sounds. – Johann Gerell Jun 08 '16 at 14:32
  • 1
    @DanielDaranas - No matter how I re-read the question, I cannot interpret the 3 bullets as anything _but_ concrete examples of what I'm after. Do you you disagree? If so, why? Note that all questions in those 3 bullets have been diligently answered here already, so they _can_ be answered. – Johann Gerell Jun 08 '16 at 14:34
  • @JohannGerell This [related question](http://stackoverflow.com/questions/1853243/c-do-you-really-write-exception-safe-code) also had interesting bullets, but it was closed as not constructive. In general, one SO question should ask about one specific software problem, not several templates of different possible problems. – Daniel Daranas Jun 08 '16 at 15:49
  • for anyone who might wonder why you would choose to be in the "we don't use exceptions"-camp: I have some embedded hardware laying around where it is *physically impossible* to use exceptions. aka you **have to** use the `-fno-exceptions` flag otherwise the device goes into a "i have no clue what the heck im doing"-mode whenever an exception is thrown – Olle Kelderman Jun 14 '16 at 21:55

6 Answers6

79

I will answer for myself and my corner of the world. I write c++14 (will be 17 once compilers have better support) latency critical financial apps that process gargantuan amounts of money and can't ever go down. The ruleset is:

  • no exceptions
  • no rtti
  • no runtime dispatch
  • (almost) no inheritance

Memory is pooled and pre-allocated, so there are no malloc calls after initialization. Data structures are either immortal or trivially copiable, so destructors are nearly absent (there are some exceptions, such as scope guards). Basically, we are doing C + type safety + templates + lambdas. Of course, exceptions are disabled via the compiler switch. As for the STL, the good parts of it (i.e.: algorithm, numeric, type_traits, iterator, atomic, ...) are all useable. The exception-throwing parts coincide with the runtime-memory-allocating parts and the semi-OO parts nicely so we get to get rid of all the cruft in one go: streams, containers except std::array, std::string.

Why do this?

  1. Because like OO, exception offers illusory cleanliness by hiding or moving the problem elsewhere, and makes the rest of the program harder to diagnose. When you compile without "-fno-exceptions", all your clean and nicely behaved functions have to endure the suspicion of being failable. It is much easier to have extensive sanity checking around the perimeter of your codebase, than to make every operation failable.
  2. Because exceptions are basically long range GOTOs that have an unspecified destination. You won't use longjmp(), but exceptions are arguably much worse.
  3. Because error codes are superior. You can use [[nodiscard]] to force calling code to check.
  4. Because exception hierarchies are unnecessary. Most of the time it makes little sense to distinguish what errored, and when it does, it's likely because different errors require different clean-up and it would have been much better to signal explicitly.
  5. Because we have complex invariants to maintain. This means that there are code, however deep down in the bowels, that need to have transnational guarantees. There are two ways of doing this: either you make your imperative procedures as pure as possible (i.e.: make sure you never fail), or you have immutable data structures (i.e.: make failure recovery possible). If you have immutable data structures, then of course you can have exceptions, but you won't be using them because when you will be using sum types. Functional data structures are slow though, so the other alternative is to have pure functions and do it in an exception-free language such as C, no-except C++, or Rust. No matter how pretty D looks, as long as it isn't cleansed of GC and exceptions, it's an non-option.
  6. Do you ever test your exceptions like you would an explicit code path? What about exceptions that "can never happen"? Of course you don't, and when you actually hit those exceptions you are screwed.
  7. I have seen some "beautiful" exception-neutral code in C++. That is, it performs optimally with no edge cases regardless of whether the code it calls uses exceptions or not. They are really hard to write and I suspect, tricky to modify if you want to maintain all your exception guarantees. However, I have not seen any "beautiful" code that either throws or catches exceptions. All code that I have seen that interacts with exceptions directly have been universally ugly. The amount of effort that went into writing exception-neutral code completely dwarfs the amount of effort that was saved from the crappy code that either throws or catches exceptions. "Beautiful" is in quotes because it is not actual beauty: it is usually fossilized because editing it requires the extra burden of maintaining exception-neutrality. If you don't have unit tests that deliberately and comprehensively misuse exceptions to trigger those edge cases, even "beautiful" exception-neutral code decays into manure.
KevinZ
  • 3,036
  • 1
  • 18
  • 26
  • 5
    I appreciate the effort put into this answer. – Johann Gerell Jun 10 '16 at 15:27
  • 1
    "Do you ever test your exceptions like you would an explicit code path?" - Absolutely; the exception (sic!) being dynamic alloc failures. – Johann Gerell Jun 10 '16 at 15:34
  • 3
    Fantastic answer - you sound like a game programmer. In my own experience, very few exception events were recoverable, so exception processing was ugly code & overhead that served no practical purpose. It was always better to code away the possibility of an exception or punt (because what else can you do?). – Rob Craig Jun 14 '16 at 21:36
  • @KevinZ _"... Data structures are either immortal ..."_ - meant _immutable_? :) – Johann Gerell Jun 15 '16 at 07:30
  • @KevinZ Although not related to the crux of my question (your interaction with the standard library, if any), I'm quite interested in hearing about your constructors. Can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code (since you mention immutable objects, I doubt that) upon failure (which the constructor can't)? Or what's your strategy there? – Johann Gerell Jun 15 '16 at 07:52
  • 1
    @JohannGerell I do mean "immortal", as in, it is constructed at startup with an explicit initializer. For these data structures, allocation and initialization are indeed 2 separate steps. Allocations are done through std::aligned_alloc(), and allocation failures mean exit(1). Initializations cannot fail. These data structures are assumed to be alive for the lifetime of the program and really do not need to have destructors. – KevinZ Jun 15 '16 at 14:04
  • Ah, yes, "immortal" does make sense in the light of your answer's context. – Johann Gerell Jun 15 '16 at 14:15
  • @JohannGerell We divide our data structures into 3 categories: immortal, value, and object. The "immortal" category is described in the previous comment. The "value" category is similar to the FP definition of a value (referential transparency), but has the additional constraint that it is a POD. For example, the data structure that represents an order will be a "value", but if it needed persistence, the value will be copied onto a space granted by a memory pool. The pool would be "immortal". The "object" category is what a Java programmer would consider an object. We do not use those. – KevinZ Jun 15 '16 at 14:19
  • 3
    Accepting this answer for its merits on the topic of how the standard library is used, not why exceptions aren't used. – Johann Gerell Jun 16 '16 at 09:43
  • How do you deal with code which calls a lot of functions which can result in an error? Do you have something better than a lot of `if`s? – geza Jul 09 '18 at 16:40
  • @geza Basically, yeah. Usually, they are encapsulated in a function that returns an ADT. These functions exist on the perimeter of the program, where their performance does not matter but their explicit handling of errors does matter. – KevinZ Sep 02 '18 at 02:44
  • How can anything be considered "exceptional" knowing that a machine is reproducible and repetitive ? To answer this, I would say that even C++ programmers can be left politically minded sometimes, searching for a social way to solve out of range array accesses ;) Thanks for this post dude. – Cevik Jan 12 '22 at 17:00
  • I've always said that the use of exceptions in C++ should in itself be an exception. It's refreshing to finally see someone who does not dogmatically praise the use of exceptions. – Hyena Aug 31 '23 at 19:00
21

In our case, we disable the exceptions via the compiler (e.g -fno-exceptions for gcc).

In the case of gcc, they use a macro called _GLIBCXX_THROW_OR_ABORT which is defined as

#ifndef _GLIBCXX_THROW_OR_ABORT
# if __cpp_exceptions
#  define _GLIBCXX_THROW_OR_ABORT(_EXC) (throw (_EXC))
# else
#  define _GLIBCXX_THROW_OR_ABORT(_EXC) (__builtin_abort())
# endif
#endif

(you can find it in libstdc++-v3/include/bits/c++config on latest gcc versions).

Then you juste have to deal with the fact that exceptions thrown just abort. You can still catch the signal and print the stack (there is a good answer on SO that explains this), but you have better avoid this kind of things to happen (at least in releases).

If you want some example, instead of having something like

try {
   Foo foo = mymap.at("foo");
   // ...
} catch (std::exception& e) {}

you can do

auto it = mymap.find("foo");
if (it != mymap.end()) {
    Foo foo = it->second;
    // ...
}
cmourglia
  • 2,423
  • 1
  • 17
  • 33
  • 3
    Google's C++ Style Guide states that they do not use [exceptions](https://google.github.io/styleguide/cppguide.html#Exceptions) and gives reasoning. – Jonathan Leffler Jun 14 '16 at 19:05
  • @Zouch I didn't know of `_GLIBCXX_THROW_OR_ABORT` - thanks for that. Although not related to the crux of my question (your interaction with the standard library, if any), I'm quite interested in hearing about your constructors. Can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code upon failure (which the constructor can't)? Or what's your strategy there? – Johann Gerell Jun 15 '16 at 07:55
  • We try to write our code to avoid being subject to exceptions (if checks are needed we use stuff like what I've shown in my answer). The only real problem that might occur is on new. In this case we had two choices: or use something like `Foo* foo = new (std::nothrow) Foo; if (!foo) ...`, or just let the program abort. In our case, crashing on most allocation failures is fine for us, so we do not deal with this. If needed, yes we would not use constructors but an init function that returns an error code. – cmourglia Jun 15 '16 at 11:34
  • @JohannGerell: I think it would be helpful if you were to add your text _“your constructors. Can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code upon failure (which the constructor can't)?”_ to your question. (It is, confusingly, quoted in an answer.) – PJTraill Jun 16 '16 at 08:57
  • @PJTraill - Thanks, added a 4th bullet to the question. – Johann Gerell Jun 16 '16 at 09:11
19

I also want to point out, that when asking about not using exceptions, there's a more general question about standard library: Are you using standard library when you're in one of the "we don't use exceptions" camps?

Standard library is heavy. In some "we don't use exceptions" camps, like many GameDev companies for example, better suited alternatives for STL are used - mostly based on EASTL or TTL. These libraries don't use exceptions anyway and that's because eighth generation consoles didn't handle them too well (or even at all). For a cutting edge AAA production code, exceptions are too heavy anyway, so it's a win - win scenario in such cases.

In other words, for many programmers, turning exceptions off goes in pair with not using STL at all.

abl
  • 211
  • 1
  • 6
  • 1
    Indeed. EASTL allows you to disable exceptions, and other big name game dev companies like Insomniac famously don't use STL – Nasser Al-Shawwa Jun 08 '16 at 12:52
  • 2
    In general, I think that forcing yourself to always stick to std is not a good idea. Standard library was designed in mind to be universal. And being universal costs - compilation and run time, memory. So every time you exactly know what you need, and its just a specific piece of possibilities that std offers, you should search for alternatives. Example: there are plenty of memory allocators out there, and nearly every one is better than std. That's because they have no requirement of being fairly good in every context (like std) but need to be extremely good at only one, specific application. – abl Jun 08 '16 at 14:21
  • 1
    is this still true for current gen (Xbox One, Playstation 4)? – nonsensation Jun 08 '16 at 19:15
13

Note I use exceptions... but I have been forced not to.

Are you trying to never call a function that can throw? (I can't see how that'd scale, so I'm very interested to hear how you accomplish this if this is the case)

This would probably be infeasible, at least on a large scale. Many functions can land up throwing, avoid them entirely cripples your code base.

Are you ok with the standard library throwing and you treat "we don't use exceptions" as "we never throw exceptions from our code and we never catch exceptions from other's code"?

You pretty much have to be ok with that... If the library code is going to throw an exception and your code is not going to handle it, termination is the default behaviour.

Are you disabling exception handling altogether via compiler switches? If so, how does the exception-throwing parts of the standard library work?

This is possible (back in the day it was sometime popular for some project types); compilers do/may support this, but you will need to consult their documentation for what the result(s) would and could be (and what language features are supported under those conditions).

In general, when an exception would be thrown, the program would need to abort or otherwise exit. Some coding standards still require this, the JSF coding standard comes to mind (IIRC).

General strategy for those who "don't use exceptions"

Most functions have a set of preconditions that can be checked for before the call is made. Check for those. If they are not met, then don't make the call; fall back to whatever the error handling is in that code. For those functions that you can't check to ensure the preconditions are met... not much, the program will likely abort.

You could look to avoid libraries that throw exceptions - you asked this in the context of the standard library, so this doesn't quite fit the bill, but it remains an option.

Other possible strategies; I know this sounds trite, but pick a language that doesn't use them. C could do nicely...

...crux of my question (your interaction with the standard library, if any), I'm quite interested in hearing about your constructors. Can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code upon failure (which the constructor can't)? Or what's your strategy there?

If constructors are used, there are generally two approaches that are used to indicate the failure;

  1. Set an internal error code or enum to indicate the failure and what the failure is. This can be interrogated after the object's construction and appropriate action taken.
  2. Don't use a constructor (or at least only construct what cannot fail in the constructor - if anything) and then use an init() method of some sort to do (or complete) the construction. The member method can then return an error if there is some failure.

The use of the init() technique is generally favored as it can be chained and scales better than the internal "error" code.

Again, these are techniques that come from environments where exceptions do not exist (such as C). Using a language such as C++ without exceptions limits its usability and the usefulness of the breadth of the standard library.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • Good points, Niall. Although not related to the crux of my question (your interaction with the standard library, if any), I'm quite interested in hearing about your constructors. Can they fail, or do you by convention use a 2-step construction with a dedicated init function that can return an error code upon failure (which the constructor can't)? Or what's your strategy there? – Johann Gerell Jun 15 '16 at 07:53
  • 1
    @JohannGerell. I've update the answer with some thoughts on the questions in the comments. – Niall Jun 15 '16 at 08:19
11

Not trying to fully answer the questions you have asked, I will just give google as an example for code base which does not utilize exceptions as a mechanism to deal with errors.

In Google C++ code base, every functions which may fail return a status object which have methods like ok to specify the result of the callee.
They have configurated GCC to fail the compilation if the developer ignored the return status object.

Also, from the little open source code they provide (such as LevelDB library), it seems they are not using STL that much anyway, so exception handling become rare. as Titus Winters says in his lectures in CPPCon, they "Respect the standard, but don't idolize it".

David Haim
  • 25,446
  • 3
  • 44
  • 78
  • 1
    This can now be done in a cross-platform manner by us all thanks to [[nodiscard]] being introduced in C++17 https://en.cppreference.com/w/cpp/language/attributes/nodiscard – pratikpc Jul 21 '21 at 18:36
2

I think this is an attitude question. You need to be in the camp of "I don't care if something fails". This usually results in code, for which one needs a debugger (at the customer site) to find out, why suddenly something is not working anymore. Also potentially people which are doing software "engineering" in this way, do not use very complex code. E.g. one would be unable to write code, which relies on the fact that it is only executed, if all n resources it relies on have been successfully allocated (while using RAII for these resources). Thus: Such coding would result in either:

  • an unmanageable amount of code for error handling
  • an unmanageable amount of code to avoid executing code, which relies on successful allocation of some resources
  • no error handling and thus considerable higher amount of support and developer time

Note, that I'm talking about modern code, loading customer-provided dlls on demand and using child processes. There are many interfaces on which something can fail. I'm not talking about some replacement for grep/more/ls/find.