Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have written an open source and cross-platform C++ File Library which have exception and error codes. Exceptions can be disabled when the program is running. In that case, the user have to check the error codes. Of course, when exception throwing is enabled, error code won't be returned because exception is thrown before the called function returns.

Currently, exceptions still could be thrown when disabled (bug). I'll fix that soon. Someone comment to me that it is considered bad design to have both exception and error codes. I agree with him and I am about to remove the error codes and have library throw custom derived exceptions with more error information.

But I hesitate. I like to have this hybrid approach for performance reasons. This is what I am thinking of doing: keeping exception throwing and error codes but exception throwing is disabled through macro at compile time instead of runtime.

If user define the following in a common config file

#define ELMAX_EXCEPTION 1

The follow code

int Write(std::wstring str) ELMAX_THROW_OR_NOT;

would expand to either of the 2.

// under C++11 compiler
int Write(std::wstring str) noexcept;

// under C++98 compiler
int Write(std::wstring str) throw();

If ELMAX_EXCEPTION is not defined or is zero, the macro would expand to nothing.

int Write(std::wstring) ;

The reason I want to do this, is for library user who wants the performance gain of not having the compiler generating the stack unwinding code (for exception) and compiler can better optimize such functions. My C++ file library is using C file API and the only thing which could throw exception is new keyword, which I intend to add nothrow. The UTF-8 code can also throw exceptions (need to be changed too)

char *parr = new (nothrow) char[num];

if(parr==NULL)
    // handle it either through error code or throw exception (if enabled)

Please kindly advise if I should have a pure exception approach or a hybrid approach (as mentioned), for my library errors?

share|improve this question
2  
Whatever you do, please use a less generic name for your macro. At least slip in the name of the library somewhere to help prevent collisions. –  Lars Viklund Dec 30 '12 at 14:10
1  
The macro in the question is more for easy understanding. I have edited them. Thanks for the reminder. –  Shao Voon Wong Dec 31 '12 at 4:51

4 Answers 4

up vote 8 down vote accepted

The cost of throwing an exception is negligible compared to writing to a disk. In addition, return codes are more expensive, not less. Finally, nobody is going to care how fast your library is when they can't make their program have the correct semantics because their teammate who just joined the company forgot to check a return code.

The only safe return code is the one that the user can safely ignore.

share|improve this answer
    
+1. But error codes are useful when in a contained environment (contained solely inside your module). But they (error codes) should not cross an interface boundary (as this requires the user of the interface to actually check for failure (which is the biggest problem with C code)). –  Loki Astari Dec 30 '12 at 20:43
    
But from I know, is that compiler is able to generate better code if it knows the called function is guaranteed not to throw. If the user makes a conscious effort to disable the exception through preprocessor, he/she is more likely to make an effort to check the return codes. That's what I am thinking. –  Shao Voon Wong Dec 31 '12 at 4:45
    
@ShaoVoonWong: Define better code: In my opinion that is not true (even if it was possible). But unless the code is very simplistic, there is no way for the compiler to know that a particular function will not throw (you can not stop exceptions from being thrown). Static analysis of the code would only be resolvable on the most trivial of libraries that did not use any other libraries (that includes the standard library). I find it hard to imagine not using any other libraries (though possible). –  Loki Astari Dec 31 '12 at 21:25
1  
@ShaoVoonWong: The compiler doesn't have to generate handle code if it knows there is no exception. You are just writing that code out again less efficiently than the compiler by writing error code checking code. And in addition, the user will not check the return codes- it cannot be depended upon. –  DeadMG Jan 1 '13 at 15:12
    
"Return codes are more expensive" - can this be backed up? –  James Jan 2 '13 at 14:13

Its up to you, but error codes are easier to document and handle within a library like this. Exceptions can be a performance killer, if you're using them for all possible errors then you might end up with them being used for program flow - and that is very bad. In addition, I'd be worried that someone is calling your library from an app that has exception handling turned off.

Generally speaking, if you're writing a completely sealed-off component, you shouldn't be throwing exceptions outside it unless they are definitely documented as things to expect. No-one wants to know about the internals of your component so all exceptions you throw internally should be caught and then (where applicable) presented to the user as a defined error case. In most cases, returning this information as an error code is the simplest, easiest and safest approach.

The reason here is that you can throw whatever exception you like, or the system can do so for you, and the user of your component can do nothing with it. At least with error codes, you're not going to let unhandled exceptions propagate through to the user.

You must not allow exceptions to return from your component if they're "normal" errors. People say that exceptions should be for exceptional situations and they're right - once you start returning "end of file" exceptions, the user will have to wrap every last one of your method calls in an try block - which makes for really messy code. If you use exceptions, the user should be able to call your functions and never put a try block anywhere, if nothing goes wrong there won't be a problem. If you can't achieve this then the user's code will have to be littered with exception handlers for all the cases where he thinks you might throw one.

A hybrid approach can work though, error codes for most things with exceptions for all unexpected problems, but if you're doing that, you might as well stick with just error codes at the boundaries of your library.

share|improve this answer

The choice between exceptions and error codes should not be a global one, but rather based on how exceptional the error condition is with a normal usage of the library.

  • Error codes should be used for situations that are erroneous but that can easily occur with a normal usage of the library. For example, failure to open a file or failure to read an integer at the current position.
  • Exceptions should be used to report serious problems that should not occur with normal usage of the library and that the immediate caller is unlikely to be able to recover from. Examples of these are memory exhaustion or hard errors reported by the underlying filesystem (e.g. disk full).

And for I/O libraries, you can also consider using a delayed error reporting mechanism. Instead of having each function return an error code (which has to be checked by the caller), the error code is stored internally. Subsequent calls do nothing if they determine an earlier call resulted in an error and the user of the library has to check (at a convenient point) if an error has occurred in the preceding set of calls. This is the default mode of operation for the standard C++ I/O streams.

share|improve this answer
    
I think your description of using error codes is flawed. They should be used when something can fail AND can be fixed within the current context. If it can't be fixed within the local context then exceptions should be used. ie. Opening a file. Can be fixed if this is a user interactive program and you can ask the user to try again but in a service this a catastrophy and can not be fixed in the local context so you need an exception (which will cause the service to stop (or log the problem at a much higher context)). –  Loki Astari Dec 30 '12 at 20:40
    
Say there are several errors in the sequence of calls, so I should store the error of last call? Example, error with opening file and it cause error at reading, so the last error is at reading file. –  Shao Voon Wong Dec 31 '12 at 4:41
    
@Shao Voon Wong: No. Because of "Subsequent calls do nothing if they determine an earlier call resulted in an error" there are no error chains. –  maaartinus Dec 31 '12 at 5:06
    
@LokiAstari, I would tend to agree, but that is typically not a judgement call that the library writer can make. If the application writer decides to turn an error code into an exception, you won't hear me complain. –  Bart van Ingen Schenau Dec 31 '12 at 11:03

If you have good use cases for both, consider splitting your library into two or three classes:

  • The core, which contains the code that does the work and is able to return enough information on failure to produce an error code or an exception.
  • A wrapper class that calls the core's methods and throws exceptions on failure. The trade-off here is that exceptions thrown won't show exactly where in the core class a failure occurred. This may not be an issue depending on the complexity of the core's methods.
  • A wrapper class that calls the core's methods and returns error codes on failure. If appropriate, the core class could be used directly, eliminating the need to have a non-throwing wrapper.

Whatever you do, don't change the library's behavior using the preprocessor. Doing so will change the method prototypes:

class Foo {
  #ifdef NO_EXCEPTIONS
    StatusType bar() { ... }
  #else
    void bar() { ... }
  #endif
};

The first time a class expecting the exception-throwing behavior #includes another's header that has the NO_EXCEPTIONS macro set, you'll have uncompilable code because there will be code present that expects Foo to be prototyped two different ways.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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