C++ – Idiomatic usage of exceptions in C++

cdesignexceptions

The isocpp.org exception FAQ states

Do not use throw to indicate a coding error in usage of a function. Use assert or other mechanism to either send the process into a debugger or to crash the process and collect the crash dump for the developer to debug.

On the other hand the standard library defines std::logic_error and all it's derivatives, which seem to me like they're supposed to handle, besides other things, programming errors. Is passing an empty string to std::stof (will throw invalid_argument) not a programming error? Is passing a string that contains different characters than '1'/'0' to std::bitset (will throw invalid_argument) not a programming error? Is calling std::bitset::set with an invalid index (will throw out_of_range) not a programming error? If these aren't, then what is a programming error that one would test for? The std::bitset string-based constructor only exists since C++11, so it should have been designed with idiomatic use of exceptions in mind. On the other hand I've had people tell me logic_error should basically not be used at all.

Another rule that comes up frequently with exceptions is "only use exceptions in exceptional circumstances". But how is a library function supposed to know which circumstances are exceptional? For some programs, being unable to open a file might be exceptional. For others, being unable to allocate memory might not be exceptional. And there's 100s of cases in-between. Being unable to create a socket? Unable to connect, or write data to a socket or a file? Unable to parse input? Might be exceptional, might not be. The function itself definitely can't generally know, it has no idea in which kind of context it is being called.

So, how am I supposed to decide if I should use exceptions or not for a particular function? It seems to me that the only actually consistent way is to use them for every and all error handling, or for nothing. And if I'm using the standard library, that choice was made for me.

Best Answer

First, I feel obliged to point out that std::exception and its children were designed a long time ago. There are a number of parts that would probably (almost certainly) be different if they were being designed today.

Don't get me wrong: there are parts of the design that have worked out pretty well, and are pretty good examples of how to design an exception hierarchy for C++ (e.g., the fact that, unlike most other classes, they all share a common root).

Looking specifically at logic_error, we have a bit of a conundrum. On one hand, if you have any reasonable choice in the matter, the advice you quoted is right: it's generally best to fail as fast and noisily as possible so it can be debugged and corrected.

For better or worse, however, it's hard to define the standard library around what you should generally do. If it defined these to exit the program (e.g., calling abort()) when given incorrect input, that would be what always happened for that circumstance--and there are actually quite a few circumstances under which this probably isn't really the right thing to do, at least in deployed code.

That would apply in code with (at least soft) real-time requirements, and minimal penalty for an incorrect output. For example, consider a chat program. If it's decoding some voice data, and gets some incorrect input, chances are a user will be a lot happier to live with a millisecond of static in the output than a program that just shuts down completely. Likewise when doing video playback, it may be more acceptable to live with producing the wrong values for some pixels for a frame or two than have the program summarily exit because the input stream got corrupted.

As for whether to use exceptions to report certain types of errors: you're right--the same operation might qualify as an exception or not, depending on how it's being used.

On the other hand, you're also wrong--using the standard library doesn't (necessarily) force that decision on you. In the case of opening a file, you'd normally be using an iostream. Iostreams aren't exactly the latest and greatest design either, but in this case they get things right: they let you set an error mode, so you can control whether failing to open a file with result in an exception being thrown or not. So, if you have a file that's really necessary for your application, and failing to open it means you have to take some serious remedial action, then you can have it throw an exception if it fails to open that file. For most files, that you'll try to open, if they don't exist or aren't accessible, they'll just fail (this is the default).

As for how you decide: I don't think there is an easy answer. For better or worse, "exceptional circumstances" isn't always easy to measure. While there are certainly cases that are easy to decide must be [un]exceptional, there are (and probably always will be) cases where it's open to question, or requires knowledge of context that's outside the domain of the function at hand. For cases like that, it may at least be worth considering a design roughly similar to this part of iostreams, where the user can decide whether failure results in an exception being thrown or not. Alternatively, it's entirely possible to have two separate sets of functions (or classes, etc.), one of which will throw exceptions to indicate failure, the other of which uses other means. If you go that route, chances are pretty good that one should be a wrapper around the other, or both act as wrappers around a shared set of functions that implement the real guts of the work.

Related Topic