10

I'm a professional C programmer and a hobbyist Obj-C programmer (OS X). Recently I've been tempted to expand into C++, because of its very rich syntax.

So far coding I haven't dealt much with exceptions. Objective-C has them, but Apple's policy is quite strict:

Important You should reserve the use of exceptions for programming or unexpected runtime errors such as out-of-bounds collection access, attempts to mutate immutable objects, sending an invalid message, and losing the connection to the window server.

C++ seems to prefer using exceptions more often. For example the wikipedia example on RAII throws an exception if a file can't be opened. Objective-C would return nil with an error sent by a out param. Notably, it seems std::ofstream can be set either way.

Here on programmers I've found several answers either proclaiming to use exceptions instead of error codes or to not use exceptions at all. The former seem more prevalent.

I haven't found anyone doing an objective study for C++. It seems to me that since pointers are rare, I'd have to go with internal error flags if I choose to avoid exceptions. Will it be too much bother to handle, or will it perhaps work even better than exceptions? A comparison of both cases would be the best answer.

Edit: Though not completely relevant, I probably should clarify what nil is. Technically it's the same as NULL, but the thing is, it's ok to send a message to nil. So you can do something like

NSError *err = nil;
id obj = [NSFileHandle fileHandleForReadingFromURL:myurl error:&err];

[obj retain];

even if the first call returned nil. And as you never do *obj in Obj-C, there's no risk of a NULL pointer dereference.

  • Imho, it would be better if you'd show a piece of Objective C code how you work with errors there. It seems, people are talking about something different than you are asking. – Gangnus Feb 8 '12 at 0:02
  • In a way I guess I am looking for justification for using exceptions. Since I have some idea of how they're implemented, I do know just how expensive they can be. But I guess if I can get a good enough argument for their use I'll go that way. – Per Johansson Feb 8 '12 at 9:59
  • It seems, for C++ you need rather justification for not to use them. According to reactions here. – Gangnus Feb 8 '12 at 10:06
  • 2
    Perhaps, but so far there hasn't been anyone explaining why they're better (except compared to error codes). I don't like the concept of using exceptions for things that are not exceptional, but it's more instinctual than based on facts. – Per Johansson Feb 8 '12 at 10:18
  • " I don't like...using exceptions for things that are not exceptional" - agreed. – Gangnus Feb 8 '12 at 10:21
1

C++ seems to prefer using exceptions more often.

I would suggest actually less than Objective-C in some respects because the C++ standard library would not generally throw on programmer errors like out-of-bounds access of a random-access sequence in its most common case design form (in operator[], i.e.) or trying to dereference an invalid iterator. The language doesn't throw on accessing an array out of bounds, or dereferencing a null pointer, or anything of this sort.

Taking programmer mistakes largely out of the exception-handling equation actually takes away a very large category of errors that other languages often respond to by throwing. C++ tends to assert (which doesn't get compiled in release/production builds, only debug builds) or just glitch out (often crashing) in such cases, probably in part because the language doesn't want to impose the cost of such runtime checks as would be required to detect such programmer mistakes unless the programmer specifically wants to pay the costs by writing code that performs such checks himself/herself.

Sutter even encourages avoiding exceptions in such cases in C++ Coding Standards:

The primary disadvantage of using an exception to report a programming error is that you don't really want stack unwinding to occur when you want the debugger to launch on the exact line where the violation was detected, with the line's state intact. In sum: There are errors that you know might happen (see Items 69 to 75). For everything else that shouldn't, and it's the programmer's fault if it does, there is assert.

That rule isn't necessarily set in stone. In some more mission-critical cases, it might be preferable to use, say, wrappers and a coding standard which uniformly logs where programmer mistakes occur and throw in the presence of programmer mistakes like trying to deference something invalid or access it out of bounds, because it might be too costly to fail to recover in those cases if the software has a chance. But overall the more common use of the language tends to favor not throwing in the face of programmer mistakes.

External Exceptions

Where I see exceptions encouraged most often in C++ (according to standard committee, e.g.) is for "external exceptions", as in an unexpected result in some external source outside the program. An example is failing to allocate memory. Another is failing to open a critical file required for the software to run. Another is failing to connect to a required server. Another is a user jamming an abort button to cancel an operation whose common case execution path expects to succeed absent this external interruption. All of these things are outside of the control of the immediate software and the programmers who wrote it. They're unexpected results from external sources that prevent the operation (which should really be thought of as an indivisible transaction in my book*) from being able to succeed.

Transactions

I often encourage looking at a try block as a "transaction" because transactions should succeed as a whole or fail as a whole. If we're trying to do something and it fails halfway through, then any side effects/mutations made to the program state generally need to be rolled back to put the system back into a valid state as though the transaction was never executed at all, just as an RDBMS which fails to process a query halfway through should not compromise the integrity of the database. If you mutate program state directly in said transaction, then you must "unmutate" it on encountering an error (and here scope guards can be useful with RAII).

The much simpler alternative is don't mutate the original program state; you might mutate a copy of it and then, if it succeeds, swap the copy with the original (ensuring the swap cannot throw). If it fails, discard the copy. This also applies even if you don't use exceptions for error handling in general. A "transactional" mindset is key to proper recovery if program state mutations have occurred prior to encountering an error. It either succeeds as a whole or fails as whole. It does not halfway succeed in making its mutations.

This is bizarrely one of the least frequently discussed topics when I see programmers asking about how to properly do error or exception handling, yet it is the most difficult of them all to get right in any software that wants to directly mutate program state in many of its operations. Purity and immutability can help here to achieve exception-safety just as much as they help with thread-safety, as a mutation/external side effect which does not occur need not be rolled back.

Performance

Another guiding factor in whether or not to use exceptions is performance, and I don't mean in some obsessive, penny-pinching, counter-productive way. A lot of C++ compilers implement what's called "Zero-Cost Exception Handling".

It offers zero runtime overhead for an error-free execution, which surpasses even that of C return-value error handling. As a trade-off, the propagation of an exception has a large overhead.

According to what I've read about it, it makes your common case execution paths require no overhead (not even the overhead that normally accompanies C-style error code handling and propagation), in exchange for heavily skewing the costs towards the exceptional paths (which means throwing is now more expensive than ever).

"Expensive" is a bit hard to quantify but, for starters, you probably don't want to be throwing a million times in some tight loop. This kind of design assumes that exceptions aren't occurring left and right all the time.

Non-Errors

And that performance point brings me to non-errors, which is surprisingly fuzzy if we look at all sorts of other languages. But I would say, given the zero-cost EH design mentioned above, that you almost certainly do not want to throw in response to a key not being found in a set. Because not only is that arguably a non-error (the person searching for the key might have built the set and expect to be searching for keys that don't always exist), but it would be enormously expensive in that context.

For example, a set intersection function might want to loop through two sets and search for keys they have in common. If failing to find a key threw, you'd be looping through and might be encountering exceptions in half or more of the iterations:

Set<int> set_intersection(const Set<int>& a, const Set<int>& b)
{
     Set<int> intersection;
     for (int key: a)
     {
          try
          {
              b.find(key);
              intersection.insert(other_key);
          }
          catch (const KeyNotFoundException&)
          {
              // Do nothing.
          }
     }
     return intersection;
}

That above example is absolutely ridiculous and exaggerated, but I have seen, in production code, some people coming from other languages using exceptions in C++ somewhat like this, and I think it's a reasonably practical statement that this is not an appropriate use of exceptions whatsoever in C++. Another hint above is that you'll notice the catch block has absolutely nothing to do and is just written to forcibly ignore any such exceptions, and that's usually a hint (though not a guarantor) that exceptions are probably not being used very appropriately in C++.

For those types of cases, some type of return value indicating failure (anything from returning false to an invalid iterator or nullptr or whatever makes sense in the context) is usually far more appropriate, and also often more practical and productive since a non-error type of case usually doesn't call for some stack unwinding process to reach the analogical catch site.

Questions

I'd have to go with internal error flags if I choose to avoid exceptions. Will it be too much bother to handle, or will it perhaps work even better than exceptions? A comparison of both cases would be the best answer.

Avoiding exceptions outright in C++ seems extremely counter-productive to me, unless you're working in some embedded system or a particular type of case which forbids their use (in which case you'd also have to go out of your way to avoid all library and language functionality that would otherwise throw, like strictly using nothrow new).

If you absolutely have to avoid exceptions for whatever reason (ex: working across C API boundaries of a module whose C API you export), many might disagree with me but I'd actually suggest using a global error handler/status like OpenGL with glGetError(). You can make it use thread-local storage to have a unique error status per thread.

My rationale for that is that I'm not used to seeing teams in production environments thoroughly check for all possible errors, unfortunately, when error codes are returned. If they were thorough, some C APIs can encounter an error with just about every single C API call, and thorough checking would require something like:

if ((err = ApiCall(...)) != success)
{
     // Handle error
}

... with almost every single line of code invoking the API requiring such checks. Yet I've not had the fortune of working with teams that thorough. They often ignore such errors half, sometimes even most, of the time. That's the biggest appeal to me of exceptions. If we wrap this API and make it uniformly throw on encountering an error, the exception cannot possibly be ignored, and in my view, and experience, that is where the superiority of exceptions lie.

But if exceptions cannot be used, then the global, per-thread error status at least has the advantage (a huge one compared to returning error codes to me) that it might have a chance to catch a former error a bit later than when it occurred in some sloppy codebase instead of outright missing it and leaving us completely oblivious about what happened. The error might have occurred a few lines before, or in a previous function call, but provided the software hasn't crashed yet, we might be able to start working our way backwards and figuring out where and why it occurred.

It seems to me that since pointers are rare, I'd have to go with internal error flags if I choose to avoid exceptions.

I wouldn't necessarily say pointers are rare. There are even methods now in C++11 and onwards to get at the underlying data pointers of containers, and a new nullptr keyword. It's generally considered unwise to use raw pointers to own/manage memory if you can use something like unique_ptr instead given how critical it is to be RAII-conforming in the presence of exceptions. But raw pointers that don't own/manage memory aren't necessarily considered so bad (even from people like Sutter and Stroustrup) and sometimes very practical as a way to point to things (along with indices that point to things).

They're arguably no less safe than the standard container iterators (at least in release, absent checked iterators) which will not detect if you try to dereference them after they're invalidated. C++ is still unashamedly a bit of a dangerous language, I'd say, unless your specific use of it wants to wrap everything and hide even non-owning raw pointers away. It is almost critical with exceptions that resources conform to RAII (which generally comes at no runtime cost), but other than that it's not necessarily trying to be the safest language to use in favor of avoiding costs that a developer doesn't explicitly want in exchange for something else. The recommended use isn't trying to protect you from things like dangling pointers and invalidated iterators, so to speak (otherwise we'd be encouraged to use shared_ptr all over the place, which Stroustrup vehemently opposes). It's trying to protect you from failing to properly free/release/destroy/unlock/clean up a resource when something throws.

14

Here's the thing: Because of C++'s unique history and flexibility, you can find someone proclaiming virtually any opinion about any feature that you'd like. However, in general, the more that what you're doing looks like C, the worse an idea it is.

One of the reasons that C++ is much looser when it comes to exceptions is that you can't exactly return nil whenever you feel like it. There's no such thing as nil in the vast majority of cases and types.

But here's the simple fact. Exceptions do their work automatically. When you throw an exception, RAII takes over and everything is handled by the compiler. When you use error codes, you have to remember to check them. This inherently makes exceptions significantly safer than error codes- they check themselves, as it were. In addition, they're more expressive. Throw an exception and you can get out a string telling you what the error is, and can even contain specific information, like "Bad parameter X which had value Y instead of Z". Get an error code of 0xDEADBEEF and what, exactly, went wrong? I sure hope the documentation is complete and up-to-date, and even if you get "Bad parameter", it's never going to tell you which parameter, what value it had, and what value it should have had. If you catch by reference, as well, they can be polymorphic. Finally, exceptions can be thrown from places where error codes never can, like constructors. And how about generic algorithms? How is std::for_each going to handle your error code? Pro-tip: It isn't.

Exceptions are vastly superior to error codes in every respect. The real question is in exceptions vs assertions.

Here's the thing. Nobody can know in advance what pre-conditions your program has for operating, what failure conditions are unusual, which can be checked for beforehand, and which are bugs. This generally means that you cannot decide beforehand whether a given failure should be an assertion or an exception without knowing the program logic. In addition, an operation which can continue when one of it's sub-operations fail is the exception, not the rule.

In my opinion, exceptions are there to be caught. Not necessarily immediately, but eventually. An exception is a problem that you expect the program to be able to recover from at some point. But the operation in question can never recover from a problem which warrants an exception.

Assertion failures are always fatal, unrecoverable errors. Memory corruption, that sort of thing.

So when you can't open a file, is it an assertion or exception? Well, in a generic library, then there are plenty of scenarios where the error can be handled, for example, loading a configuration file, you might simply use a pre-built default instead, so I'd say exception.

As a footnote, I'd like to mention there's some "Null Object Pattern" thing going around. This pattern is terrible. In ten years, it will be the next Singleton. The number of cases in which you can produce a suitable null object is miniscule.

  • The alternative isn't necessarily a simple int. I'd be more likely to use something akin to NSError which I'm used to from Obj-C. – Per Johansson Feb 7 '12 at 23:57
  • He is comparing not to C, but to Objective C. It is a big difference. And his error codes are explaining lines. Al that you say is correct, only not answers on this question somehow. No offence meant. – Gangnus Feb 7 '12 at 23:59
  • Anyone using Objective-C will of course tell you that you are absolutely, completely wrong. – gnasher729 Dec 26 '18 at 23:23
5

Exceptions were invented for a reason, which is to avoid having all your code look like this:

bool success = function1(&result1, &err);
if (!success) {
    error_handler(err);
    return;
}

success = function2(&result2, &err);
if (!success) {
    error_handler(err);
    return;
}

Instead, you get something that looks more like the following, with one exception handler way up in main or otherwise conveniently located:

result1 = function1();
result2 = function2();

Some people claim performance benefits to the no exception approach, but in my opinion readability concerns outweigh minor performance gains, especially when you include execution time for all the if (!success) boilerplate you have to sprinkle everywhere, or the risk of harder to debug segfaults if you don't include it, and considering the chances of exceptions happening are relatively rare.

I don't know why Apple discourages the use of exceptions. If they are trying to avoid propagating unhandled exceptions, all you really accomplish is people using null pointers to indicate exceptions instead, so a programmer mistake results in a null pointer exception instead of a much more useful file not found exception or whatever.

  • 1
    This would make more sense if code without exceptions actually look like that, but it usually doesn't. This pattern appears when error_handler never returns, but seldom otherwise. – Per Johansson Feb 8 '12 at 10:20
1

In the post that you reference, (exceptions vs error codes), I think there is a subtly different discussion going on. The question there seems to be whether you have some global list of #define error codes, probably complete with names like ERR001_FILE_NOT_WRITEABLE (or abbreviated, if you're unlucky). And, I think the main point in that thread is that if you're programming in a polymporphic language, using object instances, such a procedural construct is not necessary. Exceptions can express what error is occurring simply by virtue of their type, and they can encapsulate information like what message to print out (and other things as well). So, I'd take that conversation as one about whether you should program procedurally in an object oriented language or not.

But, the conversation of when and how much to rely on exceptions for handling situations that arise in code is a different one. When exceptions are thrown and caught, you are introducing a completely different control flow paradigm from the traditional call stack. Exception throwing is basically a goto statement that wrenches you out of your call stack and sends you to some indeterminate location (wherever your client code decides to handle your exception). This makes exception logic very hard to reason about.

And so, there is a sentiment like that expressed by jheriko that these should be minimized. That is, let's say that you're reading a file that may or may not exist on disk. Jheriko and Apple and those who think as they do (myself included) would argue that throwing an exception is not appropriate - the absence of that file is not exceptional, it's expected. Exceptions shouldn't substitute for normal control flow. It's just as easy to have your ReadFile() method return, say, a boolean, and to have client code see from the return value false that the file was not found. Client code can then tell the user file was not found, or quietly handle that case, or whatever it wants to do.

When you throw an exception, you're forcing a burden on your clients. You're giving them code that will, sometimes, wrench them out of the normal call stack and force them to write additional code to prevent their application from crashing. Such a powerful and jarring burden should only be forced on them if absolutely necessary at runtime, or in the interests of the "fail early" philosophy. So, in the former case, you can throw exceptions if the purpose of your application is to monitor some network operation and someone unplugs the network cable (you're signalling a catastrophic failure). In the latter case, you may throw an exception if your method expects a non-null parameter and you're handed null (you're signaling that you're being used inappropriately).

As for what to do instead of exceptions in the object-oriented world, you have options beyond the procedural error code construct. One that spring immediately to mind is that you can create objects called OperationResult or some such thing. A method can return an OperationResult and that object can have information about whether the operation succeeded or not, and whatever other information you want it to have (a status message, for example, but also, more subtly, it can encapsulate strategies for error recovery). Returning this instead of throwing an exception allows for polymorphism, preserves control flow, and makes debugging much simpler.

  • I agree with you, but it's the reason most seem not to that made me ask the question. – Per Johansson Feb 8 '12 at 10:16
0

There is a lovely couple of chapters in Clean Code which talks about this very subject - minus the Apple point of view.

The basic idea is that you never return nil from your functions, because it:

  • Adds a lot of duplicated code
  • Makes a mess of your code - I.e.: It becomes harder to read
  • Can often result in nil-pointer errors - or access violations depending on your particular programming "religion"

The other accompanying idea is that you use exceptions because it helps to further reduce the clutter in your code, and rather than needing to create a series of error codes that eventually become difficult to track manage and maintain (particularly across multiple modules and platforms) and are a pain for your customers to decipher, you instead create meaningful messages that identify the exact nature of the problem, make debugging simpler, and can reduce your error handling code to something as simple as a basic try..catch statement.

Back in my COM development days, I had a project where every failure raised an exception, and each exception needed to use a unique error code id based on extending windows standard COM exceptions. It was a hold-over from an earlier project where error codes had been previously used, but the company wanted to go all object-oriented and jump on the COM-bandwagon without changing the way they did things too much. It only took 4 months for them to run out of error code numbers and fell to me to refactor large tracts of code to use exceptions instead. Better to save yourself the effort up front and just use exceptions judiciously.

  • Thanks, but I'm not considering returning NULL. Doesn't seem possible in the face of constructors anyway. Like I mentioned I'd probably have to use an error flag in the object. – Per Johansson Feb 8 '12 at 9:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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