19

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.

9
  • 6
    You have to read that FAQ entry very carefully. It only applies to coding errors, not invalid data, dereferencing a null object, or anything having to do with general runtime badness. In general, assertions are about identifying things that should never happen. For everything else, there are exceptions, error codes and so forth. Commented Jul 7, 2016 at 22:56
  • 1
    @RobertHarvey that definition still has the same problem - if something can be resolved without human intervention or not is only known to the upper layers of a program.
    – cooky451
    Commented Jul 7, 2016 at 23:26
  • 2
    @RobertHarvey When you're trying to define coding idioms "make up your own mind" is exactly what you want to avoid. It's the worst case, and leads to horribly inconsistent code.
    – cooky451
    Commented Jul 7, 2016 at 23:39
  • 4
    Have you attempted to do any research prior to asking this question? C++ error handling idioms are almost certainly discussed in nauseating detail on the web. One reference to one FAQ entry does not good research make. After you do your research, you'll still have to make up your own mind. Don't get me started on how our programming schools are apparently creating mindless software pattern coding robots that don't know how to think for themselves. Commented Jul 7, 2016 at 23:44
  • 2
    Which lends credence to my theory that such a rule may not actually exist. I've invited some folks from The C++ Lounge to see if they can answer your question, though every time I go in there, their advice is "Stop using C++, it will fry your brain." So take their advice at your own risk. Commented Jul 7, 2016 at 23:58

5 Answers 5

16

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.

9

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.

You may not believe this, but, well, different C++ coders disagree. That's why the FAQ says one thing but the standard library disagrees.

The FAQ advocates crashing because that will be easier to debug. If you crash and get a core dump you'll have the exact state of your application. If you throw an exception you'll lose a lot of that state.

The standard library takes the theory that giving the coder the ability to catch and possible handle the error is more important then debuggability.

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.

The idea here is that if your function does not know whether or not the situation is exceptional, it should not throw an exception. It should return an error state via some other mechanism. Once it reaches a point in the program where it knows the state is exceptional, then it should throw the exception.

But this has its own problem. If an error state is returned from a function, you might not remember to check it and the error will pass by silently. This leads some people to abandon the exceptions are exceptional rule in favor of throwing exceptions for any kind of error state.

Overall, the key point is that different people have different ideas about when to throw exceptions. You're not going to find a single cohesive idea. Even though some will people will dogmatically assert that this or that is the right way to handle exceptions, there is no single agreed upon theory.

You can throw exceptions:

  1. Never
  2. Everywhere
  3. Only on programmer errors
  4. Never on programmer errors
  5. Only during non-routine (exceptional) failures

and find someone on the internet who agrees with you. You'll have to adopt the style that works for you.

4
  • It's possibly worth noting that the suggestion to only use exceptions when circumstances are truly exceptional has been widely promoted by people teaching about languages where exceptions have poor performance. C++ isn't one of those languages.
    – Jules
    Commented Jul 8, 2016 at 7:08
  • 1
    @Jules - now that (performance) certainly deserves an own answer where you back up your claim. C++ exception performance is certainly an issue, may more, maybe less than elsewhere, but stating the "C++ isn't one of those languages [where exceptions have poor performance]" is certainly debatable.
    – Martin Ba
    Commented Jul 11, 2016 at 20:57
  • 1
    @MartinBa - compared to, say, Java, C++ exception performance is orders of magnitude faster. Benchmarks suggest performance of throwing an exception up 1 level is about 50x slower than handling a return value in C++, versus more than 1000x slower in Java. Advice written for Java in this case should not be applied to C++ without extra thought because there's more than an order of magnitude difference in performance between the two. Perhaps I should have written "extremely poor performance" rather than "poor performance".
    – Jules
    Commented Jul 12, 2016 at 8:13
  • 1
    @Jules - thanks for these numbers. (any sources?) I can believe them, because Java (and C#) need to capture the stack trace, which certainly seems like it could be really expensive. I still think you initial response is kinda misleading, because even a 50x slowdown is pretty heavy I think, esp. in a performance oriented language like C++.
    – Martin Ba
    Commented Jul 12, 2016 at 8:18
2

Many other good answers have been written, I just want to add a short point.

The traditional answer, especially when the ISO C++ FAQ was written, mainly compares "C++ exception" vs. "C-style return code". A third option, "return some type of composite value, e.g. a struct or union, or nowadays, boost::variant or the (proposed) std::expected, isn't considered.

Prior to C++11 the "return a composite type" option was usually very weak. Because, there was no move semantics so copying things in and out of a structure was potentially very expensive. It was extremely important at that point in the language to style your code towards RVO in order to get best performance. Exceptions were like an easy way to effectively return a composite type, when otherwise that would be quite difficult.

IMO, after C++11, this option "return a discriminated union", similar to the idiom Result<T, E> used in Rust nowadays, should be favored more often in C++ code. Sometimes it really is a simpler and more convenient style of indicating errors. With exceptions, there is sort of always this possibility that functions that didn't throw before could suddenly start throwing after a refactor, and programmers don't always document such stuff that well. When the error is indicated as part the return value in a discriminated union, it greatly reduces the chance that the programmer will simply ignore the error code, which is the usual criticism of the C-style error handling.

Usually Result<T, E> works kind of like boost optional. You can test, using operator bool, if it is a value or an error. And then use say operator * to access the value, or some other "get" function. Usually that access is unchecked, for speed. But you can make it so that in a debug build, the access becomes checked and an assertion makes sure that there actually is a value and not an error. This way anyone who doesn't properly check for errors will get a hard assert rather than some more insidious problem.

An additional advantage is that, unlike exceptions where if it isn't caught it just flies up the stack some arbitrary distance, with this style, when a function starts signaling an error where it didn't before, you can't compile unless the code is changed to handle it. This makes problems louder -- the traditional problem of "uncaught exception" becomes more like a compile-time error than a runtime error.

I've become a big fan of this style. Usually, I nowadays use either this or exceptions. But I try to limit the exceptions to major problems. For something like a parse error, I try to return expected<T> for instance. Things like std::stoi and boost::lexical_cast which throw a C++ exception in the event of some relatively minor problem "string could not be converted to number" seem in very poor taste to me nowadays.

8
  • 1
    std::expected is still a non-accepted proposal, right?
    – Martin Ba
    Commented Jul 15, 2016 at 7:25
  • You're right, I guess it isn't accepted yet. But there are several open source implementations floating around, and I've rolled my own a couple times I guess. It's less complicated than doing a variant type since there's only two possible states. The main design considerations are like, what exact interface do you want, and do you want it to be like Andrescu's expected<T> where the error object is actually supposed to be an exception_ptr, or do you just want to use some structure type or something like that.
    – Chris Beck
    Commented Jul 15, 2016 at 9:07
  • Andrei Alexandrescu's talk is here: channel9.msdn.com/Shows/Going+Deep/… He shows in detail how to construct a class like this and what considerations you might have.
    – Chris Beck
    Commented Jul 15, 2016 at 9:07
  • The proposed [[nodiscard]] attribute will be helpful for this error handling approach since it ensures that you don't simply ignore the error result by accident. Commented Jul 15, 2016 at 10:05
  • - yeah I knew AA's talk. I found the design pretty weird since to unpack it (except_ptr) you had to throw an exception internally. Personally I think such a tool should work completely independent of execptions. Just a remark.
    – Martin Ba
    Commented Jul 15, 2016 at 10:52
1

This is a highly subjective issue, as it is part of design. And because design is basically art, I prefer to discuss these things instead of debate (I'm not saying you are debating).

For me, exceptional cases are of two kinds - those that deal with resources and those that deal with critical operations. What can be considered critical depends on the problem at hand, and, in many cases, on the programmer's point of view.

Failure to acquire resources is a top candidate for throwing exceptions. Resource can be memory, file, network connection or anything else based on your problem and platform. Now, does failure to release a resource warrant an exception? Well, that again depends. I haven't done anything where releasing memory failed, so am not sure about that scenario. However, deleting files as part of resource release can fail, and has failed for me, and that failure is usually linked to other process having kept it opened in a multi process application. I guess other resources could fail during release like a file could, and it is usually design flaw that brings about this problem, so fixing it would be better than exception throwing.

Then comes updating resources. This point is, to me at least, closely related to the critical operations aspect of the application. Imagine a Employee class with a function UpdateDetails(std::string&) which modifies the details based on comma separated string given. Similar to release of memory failing, I find it hard to imagine assignment of member variable values failing due to my lack of experience in such domains where these might happen. However, a function like UpdateDetailsAndUpdateFile(std::string&) which does as the name indicates is to be expected to fail. This is what I call critical operation.

Now, you have to see if the so called critical operation warrants an exception to be thrown. I mean, is updating the file happening at the end, as in the destructor, or is it simply a paranoid call made after every update? Does there exist a fallback mechanism that writes unwritten objects regularly? What I am saying is, you have to evaluate the criticality of the operation.

Obviously, there are many critical operations that aren't tied to resource. If the UpdateDetails() is given wrong data, it won't update the details and the failure must be made known, so you would throw an exception here. But imagine a function like GiveRaise(). Now, if the said employee is lucky to have a pointy haired boss and won't get a raise (in programming terms, some variable's value stops this from happening), the function has essentially failed. Would you throw an exception here? What I am saying is, you have to evaluate the necessity of an exception.

For me, consistency is in terms of my design approach than usability of my classes. What I mean is, I don't think in terms of 'all Get functions must do this and all Update functions must to this' but see if a particular function appeals to a certain idea within my approach. On its surface, the classes could look sort of 'haphazard' but whenever the users (mostly colleagues from other teams) rant or ask about it, I'll explain and they seem satisfied.

I see many people who basically replace return values with exceptions because they are using C++ and not C, and that it gives a 'nice separation of error handling' etc. and urge me to stop 'mixing' languages etc. I usually stay away from such people.

1

First, as others have stated, things are not that clear cut in C++, IMHO mostly because the requirements and restraints are somewhat more varied in C++ than other languages, esp. C# and Java, that have "similar" exception issues.

I'll expose on the std::stof example:

passing an empty string to std::stof (will throw invalid_argument) not a programming error

The basic contract, as I see it, of this function is that it tries to convert it's argument to a float, and any failure to do so is reported by an exception. Both possible exceptions are derived from logic_error but not in the sense of programmer-error but in the sense of "the input cannot, ever, be converted to a float".

Here, one may say a logic_error is used to indicate that, given that (runtime) input, it's always an error to try to convert it - but it's the job of the function to determine that and tell you (via exception).

Side note: In that view, a runtime_error could be viewed as something that, given the same input to a function, could theoretically succeed for different runs. (e.g. a file operation, DB access, etc.)

Further Side Note: The C++ regex library chose to derive it's error from runtime_erroralthough there are cases where it could be classified the same as here (invalid regex pattern).

This just shows, IMHO, that grouping into logic_or runtime_ error is pretty fuzzy in C++ and doesn't really help much in the general case(*) -- if you need to handle specific errors, you probably need to catch lower than the two.

(*): That's not to say that a single piece of code shouldn't be consistent, but whether you throw runtime_ or logic_ or custom_ somethings is really not that important, I think.


To comment on both stof and bitset:

Both functions take strings as argument, and in both cases it is:

  • non-trivial to check for the caller whether a given string is valid (e.g. worst case you'd have to replicate the function logic; in the case of bitset, it's not immediately clear whether empty string is valid, so let the ctor decide)
  • It is already the responsibility of the function to "parse" the string, so it already has to validate the string, so it makes sense that it reports an error to "use" the string uniformly (and in both cases this is an exception).

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?

This statement has, IMHO, two roots:

Performance: If a function is called in a critical path, and the "exceptional" case is not exceptional, i.e. a significant amount of passes will involve throwing an exception, then paying every time for the exception-unwinding-machinery doesn't make sense, and may be too slow.

Locality of error handling: If a function is invoked and the exception is immediately caught and processed, then there is little point in throwing an exception, as the error handling will be more verbose with the catch than with an if.

Example:

float readOrDefault;
try {
  readOrDefault = stof(...);
} catch(std::exception&) {
  // discard execption, just use default value
  readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}

Here's where functions like TryParse vs. Parse come into play: One version for when the local code expects the parsed string to be valid, one version when the local code assumes that it is actually expected (i.e. non exceptional) that parsing will fail.

Indeed, stof is just (defined as) a wrapper around strtof, so if you don't want exceptions, use that one.


So, how am I supposed to decide if I should use exceptions or not for a particular function?

IMHO, you have two cases:

  • "Library" like function (reused often in different contexts): You basically can't decide. Possibly provide both versions, maybe one that reports an error and a wrappper one that converts the returned error into an exception.

  • "Application" function (specific for a blob of application code, may be reused some, but is constrained by the apps error handling style, etc.): Here, it should often be pretty clear cut. If the code path(s) calling the functions handle exceptions in a sane and useful way, use exceptions to report any(but see below) error. If the application code is more easily read and written for an error return style, by all means use that.

Of course there'll be places in between - just use what needs and remember YAGNI.


Last, I think I should get back to the FAQ statement,

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 ...

I subscribe to this for all errors that are clear indication that something is severely messed up or that the calling code clearly didn't know what it was doing.

But when this is appropriate is often highly application specific, hence see above library domain vs. application domain.

This falls back on the question on whether and how to validate calling preconditions, but I won't go into that, answer already too long :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.