views:

1057

answers:

5

What is a good design for a set of exception classes? I see all sorts of stuff around about what exception classes should and shouldn't do, but not a simple design which is easy to use and extend that does those things.

  1. The exception classes shouldn't throw exceptions, since this could lead straight to the termination of the process without any chance to log the error etc.
  2. It needs to be possible to get a user friendly string, preferable localised to their language, so that there's something to tell them before the application terminates itself if it cant recover from an error.
  3. It needs to be possible to add information as the stack unwinds, eg if an xml parser fails to parse an input stream, to be able to add that the source was from a file, or over the network, etc.
  4. Exception handlers need easy access to the information they need to handle the exception
  5. Write formatted exception information to a log file (In English, so no translations here).

Getting 1 and 4 to work together is the biggest issue I'm having, since any formatting and file output methods could potentially fail.

EDIT: So having looked at exception classes in serveral classes, and also in the question Neil linked to, it seems to be common practice to just completely ignore item 1 (and thus the boost recomendations), which seems to be a rather bad idea to me.

Anyway I thought id also post the exception class I#m thinking of using.

class Exception : public std::exception
{
public:
    //enum for each exception type, which can also be used to determin
    //exception class, useful for logging or other localisation methods
    //for generating a message of some sort.
    enum ExceptionType
    {
        //shouldnt ever be thrown
        UNKNOWN_EXCEPTION = 0,
        //same as above but has a string that may provide some info
        UNKNOWN_EXCEPTION_STR,
        //eg file not found
        FILE_OPEN_ERROR,
        //lexical cast type error
        TYPE_PARSE_ERROR,
        //NOTE: in many cases functions only check and throw this in debug
        INVALID_ARG,
        //an error occured while trying to parse data from a file
        FILE_PARSE_ERROR,
    }
    virtual ExceptionType getExceptionType()const throw()
    {
        return UNKNOWN_EXCEPTION;
    }
    virtual const char* what()throw(){return "UNKNOWN_EXCEPTION";}
};
class FileOpenError : public Exception
{
public:
    enum Reason
    {
        FILE_NOT_FOUND,
        LOCKED,
        DOES_NOT_EXIST,
        ACCESS_DENIED
    };
    FileOpenError(Reason reason, cosnt char *file, const char *dir)throw();
    Reason getReason()const throw();
    const char* getFile()const throw();
    const char* getDir ()const throw();
private:
    Reason reason;
    static const unsigned FILE_LEN = 256;
    static const unsigned DIR_LEN  = 256;
    char file[FILE_LEN], dir[DIR_LEN];
};

Point 1 is addressed since all strings are handled by copying to an internal, fixed size buffer (truncating if needed, but always null terminated).

Although that doest address point 3, however I think that point is most likly of limited use in the real world anyway, and could most likely be addressed by throwing a new exception if needed.

+3  A: 

A good design is not to create a set of exception classes - just create one per library, based on std::exception.

Adding information is fairly easy:

try {
  ...
}
catch( const MyEx & ex ) {
   throw MyEx( ex.what() + " more local info here" );
}

And exception handlers have the information they need because they are exception handlers - only the functions in the try blocks can cause exceptions, so the handlers only need to consider those errors. And not you should not really be using exceptions for general error handling.

Basically, exceptions should be as simple as possible - a bit like logfiles, which which they should have no direct connection.

This has been asked before, I think, but I can't find it right now.

anon
This one? http://stackoverflow.com/questions/1157591/c-exception-handling
GMan
That was the one I was thinking of, though re-reading it, it seems to answer slightly different questions.
anon
What's wrong with having different classes? You may want to handle them differently, or even, handle some and leave others.
jon hanson
@jon experience has taught me differently - YMMV.
anon
@jon: In general I have found that one exception per library (or functional unit) is usually more convenient. Multiple exceptions types are only needed if you need to distinguish between different exceptions at the catch point, which sounds useful but in real life rarely comes up. Note: exceptions are used to transfer control to catch block that has a much broader context than the throw point, thus error handling is more general. Error codes are more useful internally within a class far handling errors and then deciding to actually throw. It is within this context you do alternative processing
Martin York
But the point of having exceptions of different types is to be able to specify the context to which you transfer! I.e. exception of type A gets caught straight away, but exception of type B travels far up the stack.
Pavel Minaev
I prefer to derive my exceptions from std::runtime_error rather than std::exception. std::runtime_error already has the message handling features built in that std::exception does not.
Martin York
@Pavel: I think the point is to have fewer rather than a multitude of exceptions. Usually this is one (but occasionally two are needed). Remember error codes are useful when contained within the class and most of the fixing happens here before the exception happens.
Martin York
@Pavel, i agree. I think "just use one exception class" is a little prescriptive for a question asking for "a good design".
jon hanson
@martin Sorry, don't know what you are talking about re runtime_error. It appears to have no way of getting at its what() value. If you know better, please post a C++ Standard reference.
anon
@Pavel Quite. Library X throws X::Error, library Y throws Y::Error. I personally have never needed a more ornate scheme.
anon
@neil. http://www.cplusplus.com/reference/std/stdexcept/runtime_error - "The constructor takes a standard string object as parameter. This value is stored in the object, and its value is used to generate the C-string returned by its inherited member what."
jon hanson
This code example is flawed. `std::exception::what()` returns `const char *`, so `ex.what() + " more text "` doesn't concatenate the text as it would with a string object. You don't want true string objects in your exception classes because they could lead to nested exceptions.
Adrian McCarthy
@Adrian - it is also flawed in the sense that the elipsis is not valid where it is. I thought that obviously made is pseudocode - maybe I was wrong. And actually, I do want true strings in my exception classes - the possibility of a nested exception is something I live with, and would any case be a problem for any solution other than fixed length buffers, which are also not acceptable.
anon
@jon sorry, I asked for a reference from the C++ Standard.
anon
@Neil - class runtime_error derives from std::exception (19.1.6) so it gets an inherited what() method
Michael Burr
But itt doesn't say how it returns it! what() for std::exception is a PVF, and nowhere that I can find is runtime_exception specified as overriding it.
anon
It's not implementation defined what runtime_error::what() will return. 19.1.6 says the constructor has this postcondition: strcmp(what(), what_arg.c_str()) == 0
Michael Burr
Std::runtime_error: 19.1.6. The runtime_error has two constructors. One with std::string the other with C-String. The post condition of using these constructors indicate that what() will return a C-String that matches the input. See Notes: (3) and (5). So the standard does not prescribe how to achieve the result, it just specifies what the result should be (it is up to the implementation how this is achieved). Note: std::runtime_error inherits from std::exception.
Martin York
@Martin You are right about that. Still, I think I will stick to deriving directly from exception. Just call me perverse, but with exceptions I like to know exactly what is going on and not to depend on a particular implementation.
anon
@Neil Butterworth: You might want exception-throwing exceptions, but Fire Lancer explicitly asked for ways not to do this (see #1 in the original question).
Adrian McCarthy
A: 

Use virtual inheritance. This insight is due to Andrew Koenig. Using virtual inheritance from your exception's base class(es) prevents ambiguity problems at the catch-site in case someone throws an exception derived from multiple bases which have a base class in common.

Other equally useful advice on the boost site

jon hanson
How about not deriving exceptions from multiple bases in the first place? I don't mind MI in general, but I don't see any reason whatsoever to use it for exception classes.
Pavel Minaev
Is it inconceivable that an exception might be caused by more than one type of problem?
jon hanson
Someone obviously writes applications and not libraries. The structure is legal C++ and as such it will be used regardless of the fact that one does not "see any reason whatsoever to use it for exception classes". You owe Jon an upvote if not an apology – pgast 0 secs ago
pgast
+1  A: 


2: No you should not mix user interface (=localized messages) with program logic. Communication to the user should be done at an outer level when the application realises that it cannot handle the issue. Most of the information in an exception is too much of an implementation detail to show a user anyway.
3: Use boost.exception for this
5: No dont do this. See 2. The decision to log should always be at the error handling site.

Dont use only one type of exception. Use enough types so the application can use a separate catch handler for each type of error recovery needed

+1  A: 

Use a shallow hierarchy of exception classes. Making the hierarchy too deep adds more complexity than value.

Derive your exception classes from std::exception (or one of the other standard exceptions like std::runtime_error). This allows generic exception handlers at the top level to deal with any exceptions you don't. For example, there might be an exception handler that logs errors.

If this is for a particular library or module, you might want a base specific to your module (still derived from one of the standard exception classes). Callers might decide to catch anything from your module this way.

I wouldn't make too many exception classes. You can pack a lot of detail about the exception into the class, so you don't necessarily need to make a unique exception class for each kind of error. On the other hand, you do want unique classes for errors you expect to handle. If you're making a parser, you might have a single syntax_error exception with members that describe the details of the problem rather than a bunch of specialty ones for different types of syntax errors.

The strings in the exceptions are there for debugging. You shouldn't use them in the user interface. You want to keep UI and logic as separate as possible, to enable things like translation to other languages.

Your exception classes can have extra fields with details about the problem. For example, a syntax_error exception could have the source file name, line number, etc. As much as possible, stick to basic types for these fields to reduce the chance of constructing or copying the exception to trigger another exception. For example, if you have to store a file name in the exception, you might want a plain character array of fixed length, rather than a std::string. Typical implementations of std::exception dynamically allocate the reason string using malloc. If the malloc fails, they will sacrifice the reason string rather than throw a nested exception or crashing.

Exceptions in C++ should be for "exceptional" conditions. So the parsing examples might not be good ones. A syntax error encountered while parsing a file might not be special enough to warrant being handled by exceptions. I'd say something is exceptional if the program probably cannot continue unless the condition is explicitly handled. Thus, most memory allocation failures are exceptional, but bad input from a user probably isn't.

Adrian McCarthy
"The strings in the exceptions are there for debugging. You shouldn't use them in the user interface. You want to keep UI and logic as separate as possible, to enable things like translation to other languages." so where and how should such strings be generated, as you said exceptiosn are useaully stuff that cause the program to exit, or at least fail to do what the user wanted to do, so there needs to be a textural representation that is end user friendly for the vast majority of exceptions.
Fire Lancer
The catch block that handles the exception should load the UI string from resources. Something like: `catch (const syntax_error }`, where `GetText()` loads a string from your program's resources.
Adrian McCarthy
A: 

Not directly related to the design of an exception class hierarchy, but important (and related to using those exceptions) is that you should generally throw by value and catch by reference.

This avoids problems related to managing the memory of the thrown exception (if you threw pointers) and with the potential for object slicing (if you catch exceptions by value).

Michael Burr