Thread: Custom exceptions with default behaviour

  1. #1
    Internet Superhero
    Join Date
    Sep 2006
    Location
    Denmark
    Posts
    964

    Custom exceptions with default behaviour

    I've been pondering which of these 2 approaches would make for the best interface for a library: Defining custom exceptions with specific names for different error scenarios but with standard behaviour, or simply using the predefined exceptions from the STL.

    This is my current approach:

    Code:
    namespace rpp
    {
        class ConnectionError : public std::exception
        {
            public:
                ConnectionError(const std::string &p_err);
                const char* what() const throw();
            private:
                std::string m_err;
        };
        
        class AuthenticationError : public std::exception
        {
            public:
                AuthenticationError(const std::string &p_err);
                const char* what() const throw();
            private:
                std::string m_err;
        };
        
        class ParsingError : public std::exception
        {
            public:
                ParsingError(const std::string &p_err);
                const char* what() const throw();
            private:
                std::string m_err;
        };
        
        class Error : public std::exception
        {
            public:
                Error(const std::string &p_err);
                const char* what() const throw();
            private:
                std::string m_err;
        };
    }
    This seems to make for more descriptive code but it adds no functionality and the implementations are completely identical, which seems "off" to me, somehow.
    How I need a drink, alcoholic in nature, after the heavy lectures involving quantum mechanics.

  2. #2
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    It really depends on what you expect is most likely to happen after the exception is thrown. Think about the common use cases where code outside your library will need to catch your exceptions. Will they all be caught by one handler, or each caught by a separate handler? Will they be rethrown? Will some of the exceptions be caught, and others passed through? Will your library be used with other libraries that can throw exceptions? Do you expect the exception handlers to do anything with results of the what() function (eg parse it for information)?

    Given that you have a generic Error, and a series of ABCError's you might want to implement a hierarchy. Or, alternatively, eliminate the generic Error completely. Or bundle them into one class.

    It is a really bad idea to use a std::string (or any type where construction or copying can throw an exception, for that matter). That can result in calling std::terminate() immediately at the throw point rather than unwinding the stack (i.e. it negates the point of using exceptions in the first place).

    Also remember that exceptions are (well) for exceptional problems, that your library cannot recover from, but the caller really has to even if your library can't. If you have large numbers of parallel exception types, it casts questions on whether your exceptions are really being used in exceptional circumstances, and a simple error code (which the caller can choose whether to act on or ignore) might be better.

    Bear in mind that C++11 introduced noexcept to indicate non-throwing functions. You might need to update your method once practices for using that sort of thing bubble through.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  3. #3
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Use a different class only each time you want different behaviour.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    This seems to make for more descriptive code but it adds no functionality and the implementations are completely identical, which seems "off" to me, somehow.
    O_o

    Use templates to do the heavy lifting if you are going to pursue multiple types unless you are trying to handle exceptions across module boundaries. (That is doable; it is just complicated.)

    If you don't see value in specific exceptions, consider that I don't have to inspect the type of the exception, or compare the string return from `what', or examine an integer, or anything related to that nonsense to handle a particular error condition. If the `catch' is executed, the type of error I care about handling at that location did occur! I don't have to be curious or look around for such information; I simply know this information because the relevant code is being executed.

    Deciding which means of communicating an error to go with is a balancing act; don't let anybody tell you different.

    Or bundle them into one class.
    This is terrible advice as it bypasses the type system.

    You can have just one exception type if you want, but don't bind multiple errors you are interested in providing into a single exception class. That forces client code to catch that exception, examine the exception value whatever it may be, and raise the same exception again if it wasn't something that could be handled.

    It is a really bad idea to use a std::string (or any type where construction or copying can throw an exception, for that matter).
    That is exactly what the standard library exceptions, like `std::runtime_error', do and is recommended for compatibility with C++98 implementations.

    the caller really has to even if your library can't
    The called doesn't have to do anything. That is actually the benefit of having multiple types. The client only needs to `catch' types the client code knows how to deal with allowing others to travel further up the stack.

    a simple error code (which the caller can choose whether to act on or ignore) might be better
    A simple error code may be preferred over exceptions in innumerable cases, but unlike exceptions, programmers don't really get to ignore the return code when they can't do anything about the failure.

    With exceptions, code for the relevant `catch' block is simply executed.

    With errors codes, you have to check the return value, must always examine it for error conditions, and must provide relevant information with logging or returning an error code further up the stack.

    Okay, sure, I'm using "have" and "must" loosely; a programmer doesn't have to do such things; a programmer could just write crap code.

    Soma

    Code:
    typedef Detail::Exception<KMarshall> EMarshall; // This is how my exceptions look.

  5. #5
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by phantomotap View Post
    This is terrible advice as it bypasses the type system.
    I did write that particular bit in a context .... immediately after I had asked a bundle of questions about how the exceptions are expected to be handled, and provided other alternatives for consideration.

    Exceptions are often used as a "throw, forget, and let the caller worry" mechanism --- which often translates into making things easy for the library author but harder for the user code. There are ways to make it easier or harder for the caller, but that requires some understanding of what the caller needs are. As distinct from what the library needs from the caller.

    Quote Originally Posted by phantomotap View Post
    That is exactly what the standard library exceptions, like `std::runtime_error', do and is recommended for compatibility with C++98 implementations.
    You missed my point. I was concerned about having a std::string as a member of the exception classes. That means propagating the exception itself can, in itself, cause another exception to be thrown. And that causes terminate() to be called. The standard exceptions are certainly not specified as having a std::string in their member data.

    That said, having a std::string as a parameter when creating the exception can give surprises, such as a std::bad_alloc being thrown rather than your intended exception type. Being in C++98 does not mean it is a particularly good design decision.
    Last edited by grumpy; 05-25-2013 at 01:37 AM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I did write that particular bit in a context .... immediately after I had asked a bundle of questions about how the exceptions are expected to be handled, and provided other alternatives for consideration.
    O_o

    Your questions are irrelevant. Binding multiple error conditions into a single class is a bad advice; it violates the type system.

    Exceptions are often used as a "throw, forget, and let the caller worry" mechanism --- which often translates into making things easy for the library author but harder for the user code.
    I don't care about such lazy or incompetent developers.

    Besides, that doesn't change the fact that the suggestion is bad.

    That means propagating the exception itself can, in itself, cause another exception to be thrown.
    That is certainly true, but that is not considered an issue, at all, by the standard.

    Why? Well, because if you don't have enough memory to process the exception, you probably do not have enough memory to copy the exception object from the `thrown' instance to the global exception object.

    The standard exceptions are certainly not specified as having a std::string in their member data.
    That is also certainly true, but you know what? That is exactly what a lot of implementations do! I have versions of "libstdc++" and "Dinkumware" right here that use exactly that approach. Of course, not all implementations do that, but that isn't the least bit relevant.

    Do you know what else is important? The object passed to your exception class may be a stack object!

    For those who don't know what that means, you can't just keep a pointer to an object passed to your constructor class. The responsibility of forcing the extended lifetime of the object passed to the exception constructor falls to the person responsible for creating the exception class.

    We can't just store a pointer to a null-terminated string when we accept such a string as a parameter of an exception class; as the developers of the exception class, we are required to copy the null-terminated string. Our options are limited: use an allocation mechanism that does not `throw' or swallow the exception. In either case, if we are out of memory when we are forced to extend the lifetime of the object passed to our constructor, we either `throw' an exception or continue with a default object completely unrelated to the parameter the constructor was passed.

    Let's not be silly, both of these options suck so let's just for the moment concern ourselves with using a default object so the construction of our exception may complete. Transforming the exception by transforming the context removes useful debugging information, and even if we go that path, the global exception object and related paraphernalia may themselves run out of memory, and if that then occurs--which is rather likely--`std::terminate' has no chance of reporting information it may have otherwise had available.

    The only way around these issues is using an object type which doesn't `throw' by definition as a parameter to your exception classes. You could, for example, just use an `int', but by using an `int' you've moved potentially useful debugging information to a tag which is separated from the point the actual exception is raised.

    That said, having a std::string as a parameter when creating the exception can give surprises, such as a std::bad_alloc being thrown rather than your intended exception type.
    In C++98, the exception classes in the standard "stdexcept" header only uses a `const std::string &' constructor parameter.

    Being in C++98 does not mean it is a particularly good design decision.
    Yes. Let's take a moment to talk about particularly good exception design decisions.

    Here is one: derive all your exception classes from the most relevant standard exception. (Consider a number class and an exception derived from `std:verflow_error', for example.)

    You should do this simply by default, but you should plan on it especially if you plan on using a `const std::string &' parameter and a `what' method.

    If you derive your exception class from one of the classes in the "stdexcept" header you are expected and required to call a relevant constructor.

    In C++98, you wind up calling the `const std::string &' constructor even if your own class uses `const char *'.

    In C++11, the exceptions in the "stdexcept" header also has a `const char *' parameter which allows you to avoid `std::string' to a point.

    Soma
    Last edited by phantomotap; 05-25-2013 at 08:35 AM.

  7. #7
    Internet Superhero
    Join Date
    Sep 2006
    Location
    Denmark
    Posts
    964
    I've gone with grumpys proposal of making a hierarchy of exceptions. The library im making is a wrapper for Reddits JSON API so there is generally two kinds of errors i can encounter: Errors returned from Reddit after making an API request, and local errors (parsing the response might fail, or no connection could be established etc). Also i've decided to include more than just an error string, for connection errors CURL provides an error code that i can include in the exception, and the Reddit API also has different error codes that could be useful for the user. (Edit: for the user of the library that is, not for the end-user)

    I'm still using std::string however, are there really any alternatives that don't suck? I can't see any way this code could be used in a memory-constrained environment, and how often does std::string fail to allocate nowadays?

    I really want to avoid having a static size for the error messages, so the only alternative to std::string seems to be allocating a char array with new using nothrow, but then what if that fails, back to square one.
    How I need a drink, alcoholic in nature, after the heavy lectures involving quantum mechanics.

  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I'm still using std::string however, are there really any alternatives that don't suck?
    O_o

    [Edit]
    Also, I completely ignored something from your first post. Rather than address that here, I've sent you a private message which I think will keep the discussion relevant to your new concerns, and you can just ignore the message in any event.
    [/Edit]

    No. You can do some stuff as related to what tricks compiler and library implementers are allowed to do that you would be unwise to implement directly, but I'm convinced that using such tricks directly actually suck more than using `std::string'.

    The solution, in a way, is to inherit from an appropriate exception from the "stdexcept" header, such as `std::runtime_error', instead of directly from `std::exception'. While many standard library implementations do use a `std::string' member variable, this is, as grumpy correctly states, not required by the standard. As we move to C++11 adoption, more implementations are using other means to store the string passed to the standard exception classes. Of these, it is common to use TLS or similar to set aside a storage area which during significant failure will be unallocated, still reserved, so that it might be reallocated to the exception mechanism, navigating and processing the `atexit' list, or whatever else is appropriate in the given context. If you inherit from one of the relevant standard exceptions, your own code benefits from these implementation improvements as they become available.

    I can't see any way this code could be used in a memory-constrained environment, and how often does std::string fail to allocate nowadays?
    As I've alluded to a few times, failure to allocate with `new' is not necessarily the same as a real out of memory condition. If you don't have enough memory available to allocate for a reasonably sized error message, odds are that more stuff will quickly go wrong as the exception mechanism will call the copy-constructor of an allocated object to store the exception itself. In other words, if you have a real out of memory situation significant enough that allocating a 20 byte string fails the allocation of the new exception object itself may fail. (Compilers use tricks to prevent this, but at the end of the day, a real out of memory condition is a very different beast than failure to allocate a 10 MiB array to process a file.)

    In practice, I'd say that it is far better to bet on `std::string' succeeding rather than failing even during a casual out of memory situation.

    Soma

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Probably the main thing to realise is that carrying around string data is not the purpose of exceptions (which is what I suspect iMalc was referring to in his oneliner, and Soma - somewhat - in his objection to "bundling up" exception types).

    The type of the error itself is often the main information needed by the caller. There is (noting that one of the errors described by the OP is an authentication error) a difference between the caller needing to be informed "An authentication error occurred" versus "User Pinnochio triggered an authentication error when attempting access to /etc/passwd". The first form can be done by the type of the exception, and does not require string data to be carried around at all. The second might, except that is debatable whether the caller needs to receive that string data .....

    If an application attempts something via a library, then there are two broad ways an authentication error might occur - those initiated under control of the application itself (e.g. application obtains username, filename, and (hashed) password, and then attempts to access the file using that information) and those initiated internally by the library (e.g. the application requests data from a file, and the library itself attempts to access the file as a user Pinnochio of the wheel group).

    If the application has provided the information (username, filename, hashed password) to the library, the information needed for recovery is already in the call stack. All the caller needs to receive is advice that an authentication error occurred, so it can attempt to correct the information it provided in the first place. It doesn't need to be given back the data that it provided to the library. It just needs to know that the data it provided triggered an authentication error. Security systems are inherently less secure if they give pointers about what is wrong (eg reporting user does not exist versus a generic authentication error allows a hostile to focus resources on finding a valid username rather than a username/password combination) so it is probably not necessary to provide more specific information to the caller than that.

    In the second case, the library needs to handle the problem internally, rather than startling an application (or its user) with a spurious authentication error. If it really, really can't do that, then the exception it needs to throw is not an authentication error - it is more likely to be some form of exception indicating a misconfiguration problem (whereby the application might inform the user that the installation is broken so there is a need to reinstall.). Either way, the application cannot fix the problem independently of the library (or the environment and tools installed with the library) and should not be expected to.

    Either way, the AuthenticationError exception does not need to carry string data around.

    It comes down to granularity of error conditions that are to be reported by exceptions, and whether the application can reasonably be expected to be capable of doing anything to correct the specific error conditions.
    Last edited by grumpy; 05-25-2013 at 11:11 PM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. C++ Exceptions
    By siavoshkc in forum C++ Programming
    Replies: 4
    Last Post: 08-14-2009, 01:03 PM
  2. Exceptions in C
    By sergioms in forum C Programming
    Replies: 15
    Last Post: 01-05-2009, 04:20 PM
  3. Including default headerfiles in custom headerfiles
    By Sentral in forum C++ Programming
    Replies: 7
    Last Post: 02-18-2006, 09:42 AM
  4. causing default exceptions to throw additional info
    By lightatdawn in forum C++ Programming
    Replies: 14
    Last Post: 08-06-2003, 06:39 PM
  5. Why exceptions???
    By ripper079 in forum C++ Programming
    Replies: 11
    Last Post: 10-15-2002, 11:34 PM