27

I am developing a C++ dongle communication library. The library would provide an unified interface to communicate with a range of remote code execution dongles like SenseLock, KEYLOK, Guardant Code.

The dongles are based on a smart card technology and have an internal file system and RAM.

A typical operation routine involves (1) enumeration of dongles connected to the USB ports, (2) connection to a dongle selected, (3) execution of the named module passing input and collecting output data.

Well, it is trivial that all of these stages can end up with an error. There can be many cases, but the most general are:

  • A dongle is not found (sure a fatal case).
  • A dongle connection failure (a fatal case).
  • The execution module specified is not found within the dongle (?).
  • The operation requested failed due to timeout (?).
  • The operation requested needs authorization (a recoverable case I suppose).
  • A memory error occurred while executing a module in a dongle (sure a fatal case).
  • A file system error occurred in a dongle (sure a fatal case).

? - I don't know yet if the case is considered fatal or not.

I am still deciding whether to throw exceptions, to return an error codes, or to implement a methods for both cases.

The questions are:

  1. Do exceptions replace the error codes completely or maybe I need to use them only for "fatal cases"?
  2. Is mixing two paradigms (exceptions and error codes) considered a good idea?
  3. Is it good idea to provide user with two conceptions?
  4. Are there any good examples of the exceptions and error codes mixing conception?
  5. How would you implement this?

Update 1.

It would be interesting to see more opinions from different perspectives, so I decided to add a 100 reputation bounty to the question.

ezpresso
  • 7,896
  • 13
  • 62
  • 94
  • 4
    Refer this http://stackoverflow.com/questions/1849490/c-arguments-for-exceptions-over-return-codes – DumbCoder Apr 27 '11 at 14:08
  • More general info on how to use exceptions: http://stackoverflow.com/questions/1989819/theory-on-error-handling – StackedCrooked Jun 05 '11 at 13:49
  • Is there a *universal* retry process for the dongles, such as "delete all temporary data on the dongle, reset dongle, and try same operation again"? – rwong Jun 08 '11 at 06:40
  • Do the dongle APIs provide error messages? Are those error messages rich in detail? (Most of the time, those API error messages are only useful for middleware developers, as users of middleware would assume those issues "have been taken care of" by the middleware. – rwong Jun 08 '11 at 06:45
  • @rwong, Thanks for your comments! The universal retry process is not implemented. The dongle object is created using dongle factory object. The connection is established by calling `dongle->Connect()` method. The underlaying C API of the dongle provides around 50 error codes and doesn't throw any exceptions. – ezpresso Jun 08 '11 at 14:24

12 Answers12

17

If we are talking about C++ application/module internal error-handling policy then my personal opinion is:

Q: Do exceptions replace the error codes completely or maybe I need to use them only for "fatal cases"?

A: They do. Exceptions are always better for C++ function than returning error codes. The reason is explained below.

Q: Is mixing two paradigms (exceptions and error codes) considered a good idea?

A: No. Mixing is ugly and makes error handling inconsistent. When I'm forced to use error-returning API (like Win32 API, POSIX, etc) I use exception throwing wrappers.

Q: Is it good idea to provide user with two conceptions?

A: No. Users are confused which variant to choose and usually make the worst decision of mixing both. Some users prefer exceptions others prefer error-returning and if all of them work on the same project they make project's error-handling practice a total mess.

Q: Are there any good examples of the exceptions and error codes mixing conception?

A: No. Show me if you find one. IMO when writing C++ code isolating error returning functions with exception throwing wrappers is the best practice if you have to use error-returning functions (and you usually do have to use them).

Q: How would you implement this?

A: In C++ code I would use exceptions only. My way is returning only in the case of success. Error-returning practice heavily messes the code with multi-level error-checking branches or more typically and even worse - error status checking is missing and thus error status is ignored that make the code full of hidden bugs that is hard to find.

Exceptions make error propagation inevitable and error handling isolated. If you need to handle some kind of error in-place it usually means that it is not an error at all but just some legitimate event that may be reported by successful return with some specific status indicated (by return value or otherwise). If you really need to check if some error occurred locally (not from the root try/catch block) you can try/catch locally so using only exceptions doesn't really limit your capabilities in any way.

Important Note:

For every particular situation it is very important to correctly define what is error and what is not - for best usability.

E.g. say we have a function that shows input dialog and return text entered by the user and if the user may cancel the input then cancel event is success - not error (but it must be somehow indicated on return that user canceled the input) but lack of resources (like memory or GDI objects or something) or something like the absence of display device to show the dialog is indeed error.

In general:

Exceptions are more natural error-handling mechanism for C++ language. So using exceptions is a good idea if you are developing C++ application or library to be used by C++ application only (not by C application, etc). Error-returning is more portable approach - you may return error codes to applications written on any programming language and even running on different computer. Of course typically OS API routines report their status via error-codes - it is natural to make them language-independent. And for that reason you have to deal with error-codes in every-day programming. BUT IMO planning error-handling policy of C++ application to be based on error-codes is just asking for trouble - the application becomes a totally unreadable mess. IMO the best way to deal with status codes in C++ application is using C++ wrapper functions/classes/methods to call error-returning functionality and if error is returned - throw exception (with all status info embedded into exception class).

Some important notes and caveats:

In order to use exceptions as error-handling policy in a project it is important to have a strict policy of writing exception safe code. It basically means that every resource is acquired in constructor of some class and more importantly released in destructor - this makes sure you don't have resource leaks. And also you have to catch exceptions somewhere - usually in your root-level function - like main or window procedure or thread procedure, etc.

Consider this code:

SomeType* p = new SomeType;

some_list.push_back(p);
/* some_list is a sequence of raw pointers so each must be delete-ed
   after removing it from this list and when clearing the list */

It is typical potential memory leak - if push_back throws an exception then dynamically allocated and constructed SomeType object is leaked.

Exception safe equivalent is this:

C++2011 and later variant:

std::unique_ptr<SomeType> pa( new SomeType );

some_list.push_back(pa.get());
pa.release();
/* some_list is a sequence of raw pointers so each must be delete-ed
   after removing it from this list and when clearing the list */

obsolete C++1998/C++2003 variant:

std::auto_ptr<SomeType> pa( new SomeType );

some_list.push_back(pa.get());
pa.release();
/* some_list is a sequence of raw pointers so each must be delete-ed
   after removing it from this list and when clearing the list */

But actually storing raw pointers is bad exception-unsafe practice in general so more appropriate C++ 2011 and later variant is:

std::shared_ptr<SomeType> pa = std::make_shared<SomeType>(); //OR std::unique_ptr

some_list.push_back(pa);
/* some_list is a sequence of smart pointer objects
   so everything is delete-ed automatically */

(in C++2003 and before you could use boost::shared_ptr or your own properly designed smart pointer)

If you are using C++ standard templates, allocators, etc. you either write exception safe code (if you try/catch every single STL call the code becomes a mess) or leave the code full of potential resource leaks (that is unfortunately happen very often). Well written C++ application is just always exception safe. Period.

Serge Dundich
  • 4,221
  • 2
  • 21
  • 16
  • Overall good advice, but your auto_ptr example is buggy. If an exception happens between the push_back and the `pa.release()`, the pointer can be freed twice (because it is now "owned" by both some_list and the auto_ptr). Correct usage is `some_list.push_back(pa.release())`. – Nemo Jun 06 '11 at 04:25
  • 1
    @Nemo: "but your auto_ptr example is buggy. If an exception happens between the push_back and the pa.release()" Exception cannot happen between the push_back and the pa.release() because there is nothing between them (and there **must** be nothing). The whole point of that code is calling release **after** push_back (not before) to make sure that `delete` is called if `push_back` generates exception. Your `some_list.push_back(pa.release())` is just equivalent to the first code with raw pointer (so it is exception unsafe for the same reason). – Serge Dundich Jun 06 '11 at 11:02
  • 4
    I think it's a bad advice for middleware to communicate using exceptions to the client. Error codes, as awkward as they are, are less intrusive. Imaging if the Win32 API was using exceptions rather than error codes. They can basically come from many different places, many different ways, and types. For internal logic, they are good choice, as you know what (documented) is thrown, and what can be handled. – malkia Jun 08 '11 at 05:48
  • 1
    @malkia: it depends on the client's coding style. In either case, if the client's coding style is different from the library's, it will have to write exception wrappers. There is one situation where middleware should not throw exception to client: when multiple compiler vendors are used, or when the library code and client code are compiled by different compiler vendors/versions. There is no compatibility specification fo exceptions; see http://stackoverflow.com/q/2619630. – rwong Jun 08 '11 at 06:52
  • 1
    @malkia: I added some more details to my answer. Exception are natural for C++ applications and C++ APIs. Of course it is bad idea to throw exceptions from DLL or from system call of some kind. And in general exceptions cannot be used for inter-something communication. Win32 API is purely C language API. May be I had to be more specific about C++ context but since the original question is tagged "C++" so I thought I was clear enough. – Serge Dundich Jun 08 '11 at 07:02
  • 1
    @rwong: "it depends on the client's coding style" Exactly. Coding style is what all this about. IMO using exceptions is C++ natural coding style. And using error-returning is C coding style (even if the program is written on C++). – Serge Dundich Jun 08 '11 at 07:06
  • 1
    @malkia: "as you know what (documented) is thrown, and what can be handled" Not really. You usually know only some root exception class (like std::exception). One of the best things about exceptions is that you don't need to know what may be thrown. You just know what kind of error you expect (and catch it) - and you don't care about other kinds of errors (and you don't have to loose error information). (to be continued) – Serge Dundich Jun 08 '11 at 07:13
  • 2
    @malkia: (continue) Status codes on the other hand may be of different kinds (like `errno`, `GetLastError()`, some third-party library error status codes, etc) and when you call functions from different APIs and get the errors you have to decide how to translate status code of that API to status code used by your function so you usually loose specific error information. With exceptions you just wrap specific error code (and all the other info given) to its exception class and loose no error information. – Serge Dundich Jun 08 '11 at 07:24
  • Thanks for the replies guys. I'm highly opinionated here, being console game developer, and not having the ability to use exceptions everywhere due to limitations on certain platforms. GetLastError() errno, as Serge is saying, is also not very reliable (information can get lost, or overwritten by other thread, making errno threadlocal is not a solution to the problem either). – malkia Jun 08 '11 at 18:02
  • @malkia: I was mentioning `GetLastError()` and `errno` as just examples of error codes (used by error-returning functions). Since they are always thread-local they are equivalent to just returning error code with slightly different mechanism. You still have to check error status after each function. – Serge Dundich Jun 09 '11 at 03:29
14

Are there any good examples of the exceptions and error codes mixing conception?

Yes, boost.asio is the ubiquitious library used for network and serial communication in C++, and nearly every function comes in two versions: exception-throwing and error-returning.

For example, iterator resolve(const query&) throws boost::system::system_error on failure, while iterator resolve(const query&, boost::system::error_code & ec) modifies the reference argument ec.

Of course what is good design for a library, is not a good design for an application: the application would do best to use one approach consistently. You're creating a library, though, so if you're up for it, using boost.asio as a model could be a workable idea.

Cubbi
  • 46,567
  • 13
  • 103
  • 169
  • 1
    And if at some point you're stuck with an `error_code` without the possibility to handle it, it is possible to convert it to an exception and throw it. – Luc Danton Jun 03 '11 at 05:52
  • That's a good compromise. On certain video game consoles for example, exceptions are not allowed (compiler/runtime limitation), there might be other cases. As such exception-only return mechanism would rule your middleware out. In general error codes are better, as C++ exception mechanisms are of many flavors implementation wise (or you would end up with all kind of binary variations for your libraries - with exceptions, without, then type of exceptions, etc.) – malkia Jun 08 '11 at 05:51
9
  • Use error codes where the application would typically continue execution from that point.
  • Use exceptions where the application would typically not continue execution from that point.

I actually mix error codes and exceptions from time to time. Contrary to some other answers, I don't think this is "ugly" or bad design. Sometimes, it's inconvenient to have a function throw an exception upon error. Suppose you don't care if it fails:

DeleteFile(filename);

Sometimes I don't care if it fails (e.g. with a "file not found" error) - I just want to make sure it's deleted. This way I can ignore the returned error code without having to put a try-catch around it.

On the other hand:

CreateDirectory(path);

If this fails, the code which follows is probably also going to fail, so the function should not continue. Here it's convenient to throw an exception. The caller, or somewhere further up the call stack, can figure out what to do.

So, just think about whether it is likely the code following it is going to make any sense if the function failed. I don't think mixing the two is the end of the world - everyone knows how to deal with both.

AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
  • 1
    Thanks for the reasonable input! – ezpresso Jun 08 '11 at 20:33
  • 1
    +1, but a note: All my uses of `CreateDirectory` care only about the result, i.e., having a directory (possibly preexisting). In this sense it's exactly like `DeleteFile` and I'd let both throw an exception (if the outcome is different from the expected one) or return an error code (if the outcome is fine). – maaartinus Dec 31 '12 at 05:27
  • How about returning with error code immediately instead of throwing an exception? and this also prevents the function from continuing execution, how do you think? – Han Feb 26 '19 at 14:33
7

Exceptions are good when the code supposed to handle the error is far away (many layers up) from the site detecting the problem.

Status codes are good if negative status is expected to be returned "often" and if the code calling your's are supposed to take care of that "problem".

Naturally, there is a large gray zone here. What is often, and what is far away?

I wouldn't recommend you to provide both alternatives, as that is mostly confusing.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
5

I must admit I appreciate the way you categorize the errors.

Most people will say that exceptions should cover exceptional cases, I prefer to translate to user-land though: unrecoverable. When something happens that you know your user cannot easily recover from, then throw an exception, this way he won't have to handle it each time it calls you but will just let it bubble up to the top of its system where it'll be logged.

The rest of the time, I go for using types that embed errors.

The simplest is the "optional" syntax. If you are looking for an object in a collection, then you might not find it. There is a single cause here: it's not in the collection. As such, error code are definitely spurious. Instead one can use:

  • a pointer (shared if you want to share ownership)
  • a pointer-like object (like an iterator)
  • a value-like nullable object (kudos to boost::optional<> here)

When things are more tricky, I tend to use an "alternative" type. It's the idea of the Either type in haskell, really. Either you return what the user asked for, Or you return an indication of why you didn't return it. boost::variant<> and the accompanying boost::static_visitor<> play nicely here (in functional programming it's done through object deconstruction in pattern matching).

The main idea is that error code can be ignored, however if you return an object which is both the result of the function XOR the error code, then it cannot be silently dropped (boost::variant<> and boost::optional<> are really great here).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
5

I'm not sold on how good an idea this is, but recently I worked on a project where they couldn't toss around exceptions, but they didn't trust error codes. So they return Error<T>, where T is whatever type of error code they would have returned (usually and int of some sort, sometimes a string). If the result went out of scope without being checked, and there was an error, an exception would get thrown. So if you knew there was nothing you could do, you can ignore the error and generate an exception as expected, but if you could do something then you can explicitly check the result.

It was an interesting mixture.

This question keeps popping to the top of the active list, so I figure I'll expand a bit on how this worked. The Error<T> class stored a type-erased exception, so its use didn't force the use of a specific exception hierarchy or anything like that, and each individual method could throw however many exceptions as it liked. You could even throw ints or whatever; pretty much anything with a copy constructor.

You did lose the ability to break on an exception being thrown, and wind up at the source of the error. However, because the actual exception is what ends up thrown [its just the location that changed], if your custom exception creates a stack trace and saves it on creation, that stack trace will still be valid whenever you get around to catching it.

One big issue that could be a real game breaker, is that the exception is thrown from within its own destructor. Which meant you ran the risk of it resulting in your application terminating. Oops.

Error<int> result = some_function();
do_something_independant_of_some_function();
if(result)
    do_something_else_only_if_some_function_succeeds();

The if(result) check ensures that the error system lists that error as handled, so there is no reason for result to throw its stored exception on destruction. But if do_something_independant_of_some_function throws, result will get destroyed before reaching that check. This results in the destructor throwing a second exception, and the program just gives up and goes home. This is extremely easy to avoid [always check the result of a function before doing anything else], but still risky.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
4

There is a major point here to consider, this is for a locking mechanism, giving out error codes as to the details of what fails is like telling a lock picker that the first 3 of the 4 pins inside the lock he has correct.

You should omit as much information here as possible, and make sure that your check routine ALWAYS takes the same amount of time to verify the card so that a timing attack is not possible.

But back to the original question, in general I prefer an exception to be raised in all cases so the calling application can decide how it wants to handle them by wrapping the call into a try/except block.

Geoffrey
  • 10,843
  • 3
  • 33
  • 46
  • I don't agree. Unlike error codes, uncaught exceptions are non-local gotos and would get caught by whoever put the "net to catch the fish", unlike error codes just "missing the fish with your fishing pole" – malkia Jun 08 '11 at 05:53
  • This is a good thing to consider in general, but I don't think it's an argument in favour of either error codes or exceptions; after all, exception classes often contain error codes inside them. – HighCommander4 Jun 08 '11 at 07:44
3

The way I do this is I have an Exception class ( just the exception object you throw ) which consists of a string message and an error code enum and some nifty constructors.

I only wrap things in a try catch block when recovery is meaningful and possible. Commonly that block will attempt to handle only one or two of the enumerated error codes while rethrowing the rest. At the top level my entire app runs in a try catch block that logs all unhandled non fatal errors, while it exits with a message box if the error is fatal.

You could alternatively have a seperate Exception class for each kind of error, but I like to have it all in one place.

  • 1
    you surely got the worst mix possible... A base class (pure virtual, like say, std::exception) and one class for each kind is definitely better if you go the exception road. – Matthieu M. Apr 27 '11 at 14:35
  • Why? Is there a large overhead for rethrowing? –  Apr 27 '11 at 23:20
  • @Matthieu M. Currently I am doing something like `try { somestuff } catch( Exception exc ) { switch( Exception.GetCode() ) case BAD: do stuff; break; case NOT_IMPLEMENTED: do stuff; break; default: throw exc` I like the elegance of a switch statement (especially the default clause) and I don't think I could do this with the method you suggest unless `try {stuff} catch( childClass1 ) { doThings } catch( childClass2 ) { doThings } catch( baseClass ) { doThings }` works as an equivalent of default. I know about catch(...) but I only want a default handler for baseClass exceptions, not others. –  Apr 27 '11 at 23:38
  • 1
    Decided to test that, and it works. So yeah what you suggest is better. –  Apr 28 '11 at 01:59
  • 2
    in general I don't worry much about performance. There would probably be some penalty in rethrowing, but I am more interested in syntax and semantics: ie making the developer's life easier :) ... The net advantage of specific exception class is that you catch only those exceptions you can treat, and let the other pass without writing any cruft. – Matthieu M. Apr 28 '11 at 06:20
3

Do exceptions replace the error codes completely or maybe I need to use them only for "fatal cases"?

reserve them for what's truly necessary. if you write it this way from the outset, there will be very few you'll need to handle/pass. keep the issues local.

Is mixing two paradigms (exceptions and error codes) considered a good idea?

i think you should base your implementation off error codes, and use exceptions in truly exceptional situations (e.g. no memory, or that you must catch one thrown to you). otherwise, prefer error codes.

Is it good idea to provide user with two conceptions?

no. not everyone uses exceptions, and they are not guaranteed to safely cross boundaries. exposing them or throwing them into the client's domain is a bad idea, and makes it difficult for clients to manage apis you provide. they will have to handle multiple exits (result codes and exceptions) and some will have to wrap your interfaces in order to use the library in their codebase. error codes (or something similar) are the lowest common denominator here.

How would you implement this?

i would not subject clients to exceptions, it's more to maintain and can be insulated. i'd use error codes or a simple type which provides additional fields where needed (e.g. a string field if you would supply a recovery suggestion for the end user). and i'd try to keep it quite minimal. also, provide a means for them to test with increased diagnostics for development.

justin
  • 104,054
  • 14
  • 179
  • 226
3

Keep exceptions local to your library/middleware, for your logic.

Use error codes instead to communicate with the client.

malkia
  • 1,389
  • 13
  • 12
2

Do exceptions replace the error codes completely or maybe I need to use them only for "fatal cases"?

Exceptions do not replace error codes universally. There are many low-level functions that have several return values that may or may not be considered errors depending on the user's context. For example, many I/O operations can return these responses:

  • EINTR: operation interrupted, sometimes it's worth restarting the operation and other times it means "user wants to exit the program"
  • EWOULDBLOCK: operation is non-blocking, but no data can be transferred now. If a caller expects reliable nonblocking behavior (e.g. UNIX domain sockets), this is a fatal error. If a caller is checking opportunistically, this is passive success.
  • partial success: In a streaming context (e.g. TCP, disk I/O), partial reads/writes are expected. In a discrete messaging context (e.g. UDP), partial reads/writes may indicate fatal truncation.

It's clear when you're working at this level that exceptions aren't appropriate because the author of the implementation cannot predict which "failure responses" will actually be considered critical failures by a given user, or just normal behavior.

Tom
  • 10,689
  • 4
  • 41
  • 50
1

This was really confusing as hell for me too in earlier days and so why I want to add an answer to this question even now because it is assuredly perennial:

  • Yes, you should generally use exceptions and not error codes to indicate errors. That is, by default, if you are going to have an error status, you should use an exception. That should be the rule.

The name is what throws one off - they are designed for error situations. Look at the C++ STL, it uses exceptions to indicate its errors like out of memory (std::bad_alloc), etc. The actual name is, I believe, meant to indicate the idea of an "exception" to the normal flow of execution, in that it branches out - perhaps all the way back to the operating system (i.e. the program crashes), not an "exception" as in "a rarity".

The exceptions (heh) to this rule are where a seeming "error" isn't actually an error. An error properly means that an operation failed to complete successfully, that is, it encountered something that prevented it from being able to produce a valid result. For example, not finding a configuration file, or not finding a file the user requests to perform an operation on, indicate an operation failure.

Where that one gets confused is that there are some things that look like "errors" but aren't, and here you should generally use a return code - typically a "null" of some form that would be put out wherever it usually gives its results: the one I have in mind is if you have, say, a "find files" function whose specific purpose is to search for files, and it returns what files it found meeting some criteria. If no files are found, that is not an error, it is just a successfully-completed search turning up null.

But if, say, you asked it to search a non-existent directory, or it lost a network connection, or it ran out of memory in building the list, or ... then that's an error. That's something that prevents it from being able to successfully perform the operation. It's not that it simply failed to find, it's that something went wrong with the "looking" process you wanted to perform. It's "something going wrong" that is what an error means, and such should always be handled as exceptions. Otherwise you'll constantly be arguing in your head over what to use and you shouldn't do that. Choose exception by default unless it looks like there's good reason not to.

The_Sympathizer
  • 1,191
  • 9
  • 17